본문으로 바로가기

1장 메모리와 데이터 타입

category 코어 자바스크립트 2020. 12. 21. 15:38

데이터 할당에 대한 메모리 영역의 변화

let a = 'abc'

 

라고 a를 선언하고 'abc'를 할당하면 내부적으로는 이런식으로 된다

1. 변수 영역에 공간을 확보해 1003에 넣어두고

2. 그 공간의 식별자를 'a'로 지정한다

3. 데이터 영역의 5004에 'abc'를 저장한다

4. 5004라는 데이터 영역의 주소를 1003의 공간에 대입한다

 

이런식으로 데이터할당이 이루어지게 된다.

불변값인 기본형 데이터는 이런식으로 이루어지게되는데 

const obj = {
   a: 1,
   b: 'bbb'
};

이런식으로 가변값인 객체를 메모리에 할당하려고하면 내부적으로 이렇게 이루어진다

기본형처럼 똑같은 방식으로 하다가

1. 데이터 영역에 데이터를 저장하려고 보니 여러개의 프로퍼티가 있어서 별도의 변수 영역(객체변수 영역)을 마련해서 그 주소를 저장 한다 그 변수의 영역은 @7103 부터이다 

2. 그 영역의 주소는 다시 변수 영역이다 처음과 똑같은 과정을 반복해서 @7103에 a를, @7104에 b를 저장한다 

3. 데이터 영역에서 숫자 1을 검색하고 없으므로 @5003에 만들어주고 그 주소를 @7103에 넣어준다 b도 마찬가지..

 

 

여기서 3번에 데이터 영역에서 먼저 숫자 1을 검색하게 되는데 찾는 값이 있으면 메모리를 낭비하지 않기위해 그 주소값을 가져다가 쓴다 

데이터 영역에 같은 값이 있는데도 새로 만들게 되면 

const a = 5; 

const b = 5;

const c = 5; ... 

똑같은 값임에도 불구하고 메모리를 계속 8byte 씩 사용하게된다

(500개 일때는 (500 * 8 byte) = 4000byte 소모)

하지만 값을 그대로 가져다 쓴다면

(500개 일때는 (500 * 2 + 8 byte)  = 1008byte 소모)   //(주소값이 2byte라고 할때) 

 

하지만 자바스크립트는 그렇게 하지 않기 위해 주소값만 할당을 하고 만약 값이 있으면 그 값을 그대로 가져다 써서 

변수영역과 데이터 영역을 분리하기 때문에 중복된 데이터에 대한 처리 효율이 높아진다

 

 

그러면 복사를 할때  b가 a의 주소값을 가져다가 쓰는데 b를 바꾸면 a가 바뀌어야 되지 않나라는 의문이 생긴다

let a = 1;
let b = a;

b = 3;
a = ? //1이 나온다

자바스크립트는 기본형이 

let b = 1;

b = 3; 

이런식으로 값이 변경되면 그 주소에 있는 1을 3으로 변경하는 것이 아니라

3이라는 데이터 영역을 다시 만들고 b는 그 주소값을 참조하게 된다 

 

 

그렇다면 객체는 복사한 후 복사본 값을 변경하면 왜 원본의 값도 변경이 되는지 의문이 생긴다

const obj1 = {a : 10};
const obj2 = obj1;

obj2.a = 20;
obj1.a = ?  // 20이 나온다

가장 큰 이유가 기본형이 아닌 참조형은 주솟값을 복사하는 과정을 한번더 거치기 때문에 그렇다

 

위에서 말했듯이 객체는 객체의 변수영역을 따로 가진다 그리고 그곳의 값을 변경 할때는 해당 주소가 변경이 된다 그러면 같은 주소를 참조하고있던 obj1과 obj2는 객체 변수영역의 주소가 같이 변경이 된다

 

 

하지만 프로퍼티를 바꾸지 않고 새 객체를 넣어 준다면

let obj1 = {a : 100};
let obj2 = obj1;

obj2 = {a : 200};
obj1 = ??   // {a : 100}이 나온다

새로 객체를 할당한다면 새로운 공간을 만들어 그곳을 참조하기 때문에

프로퍼티를 변경할때만 가변값이라는게 성립된다

 

 

배열의 메모리 할당도 객체가 객체변수 영역을 만들듯이 배열의 변수영역을 새로 만들어 인덱스 0,1,2,3,4...에 할당한다

그래서 배열도 복사한 변수의 값이 바뀌면 객체처럼 같이 바뀐다

 

 

그러면 객체의 값을 복사하고 주소를 참조하지 않는 새로운 객체를 만들고 싶을때는 어떻게 해야할까?

여기서 얕은 복사와 깊은 복사를 알아야한다

 

얕은 복사

얕은 복사는 기존 정보를 복사해서 새로운 객체를 반환해 복사를 한다

//얕은 복사를 하는 함수
const copyObject = (target) => {
  const result = {};
  for(let prop in target){
      result[prop] = target[prop];
  }
  return prop;
} 

이런식으로 하면 객체의 value가 모두 기본형일 경우에 안전하게 새로운 객체로 복사가 된다

 

하지만 value가 기본형이 아닌 중첩된 객체에서는 중첩객체의 주소를 같이 참조하게 되어 다시 문제가 생긴다

얕은 복사는 한단계 아래의 값을 복사하는 것이기 때문이다

//중첩객체의 예
const user = {
    name: 'Jaenam',
    urls:{
        portfolio:'http://github.com/abc',
        blog: 'http://blog.com',
        facebook: 'http://facebook.com/abc'
    }
}

그럴땐 깊은 복사를 사용해야한다 

 

깊은 복사

깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사 하는 방법이다

깊은 복사는 재귀함수로 구현해야한다

 

//깊은 복사를 하는 함수
const copyObjectDeep = (target) => {
    const result = {};
    if(typeof target === 'object' && target !== null) { //중첩된 객체 일때
        for(let prop in target) {
            result[prop] = copyObjectDeep(target[prop]);
        }
    } else { //기본형 일때
        result = target;
    }
    return result;
}

이런 식으로 하면 value가 중첩객체일때도 모두 새로운 객체로 복사된다

//typeof target === 'object'를 하게되면 배열과 객체를 검사하게된다 

//여기서 의문은 함수는 object가 아닐까 하지만 함수의 type은 function이다 

 

여기서 살짝 팁이 있다면 JSON을 이용하는 것이다

JSON을 이용하면 좀더 쉽게 구현 가능하다

//JSON을 이용한 깊은 복사 함수
const copyObjectViaJSON = (target) = {
    return JSON.parse(JSON.stringify(target));
}

하지만 JSON을 이용하면 __proto__와 getter/setter, 함수 등과 같이 JSON으로 변경할 수 없는 프로퍼티는 무시하게된다 

그럴 땐 위와같은 재귀함수로 구현..

 

undefined 와 null

자바스크립트 엔진은 다음 3가지 경우에 undefined를 반환한다

1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때

2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때

3. return 문이 없거나 호출되지 않는 함수의 실행 결과

 

비어있는 요소와 unedfined를 할당한 요소는 차이가 있다

//undefined와 배열

const arr1 = [];
arr1.length = 3;  // [empty x 3]

const arr2 = new Array(3) // [empty x 3]

const arr3 = [undefined, undefined, undefined] //[undefined, undefined, undefined]

배열의 크기를 3으로만 하면 undefined조차 할당이 되어 있지 않다

이처럼 비어있는 요소와 undefined를 할당한 요소는 출력결과 부터 다르다

 

비어있는 요소는 많은 배열 메서드의 순회 대상에서 제외된다

const arr1 = [undefined, 1];
const arr2 = [];
arr2[1] = 1;

arr1.forEach( (v,i) => console.log(v,i) ) // undefined 0 / 1 1
arr2.forEach( (v,i) => console.log(v,i) ) // 1 1 
//arr2의 0번째 인덱스는 순회대상에서 제외

 

그 이유는 배열이 length 프로퍼티의 개수 만큼 빈 공간을 확보하고 각 공간에 인덱스를 이름으로 지정할 것이라고 생각하기 쉽지만 실제로는 객체와 마찬가지로 특정 인덱스에 값을 지정할 때 비로소 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주솟값을 저장하는 등의 동작을 하기 때문이다

 

let a;

위처럼 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근 할때 자동으로 undefined가 a에 할당 되는 것이 아니라 a에 접근할때 자바스크립트가 undefined를 반환하게되는 것이다

 

undefined를 할당하는 것은 혼란을 가중시킨다

그래서 직접 undefined를 할당하는 것을 지양해야 혼란을 피할 수 있다

 

 

그렇다면 비어있다는 것을 명시할 때는 어떻게 해야할까?

 

undefined가 아닌 null을 사용하면 된다.

null은 애초부터 이런 용도로 만든 데이터 타입이다

하지만 null은 약간의 자체버그가 있긴하다

const n = null;
console.log(typeof n);  // object

console.log(n == undefined);  // true
console.log(n == null);  // true

null이 object라는 점이다

 

그래서 null인지 확인 할때는 

console.log(n === undefined);  // false
console.log(n === null);  // true

(===) 일치 연산자로 비교를 해주어야 한다

'코어 자바스크립트' 카테고리의 다른 글

7장 클래스  (0) 2020.12.28
6장 프로토타입  (2) 2020.12.24
5장 클로저  (0) 2020.12.23
4장 콜백함수  (0) 2020.12.22
3장 this  (2) 2020.12.21
2장 실행 컨텍스트  (0) 2020.12.21