[JavaScript] 깊은 복사와 얕은 복사

원시타입의 깊은 복사

let num = 1;
let num2 = num;

num = 3;

console.log(num);  // 3
console.log(num2);  // 1

  

1이라는 데이터가 주소값이 3001인 메모리에 있다고 가정할 때, num은 1이 저장된 주소값(3001)을 갖고있다.

num2에 num의 값을 복사하기위해 num2 = num을 실행한 경우 num2은 마찬가지로 1이 저장된 주소값(3001)을 갖게된다.

각 식별자가 독립적으로 데이터 1의 위치를 알고있으므로, 둘중 하나가 다른 데이터를 할당받더라도 나머지 하나는 그대로 1에 접근할 수 있다.

 

위 예시처럼 num = 3을 새로 대입해준 경우 num은 3이라는 데이터가 위치한 주소(ex. 3002)를 갖게되고, 이 변화는 num2가 갖고있는 정보에 영향을 미치지 않는다.

 

 

참조타입의 얕은 복사

let obj = {
    num: 1,
};

let obj2 = obj;

obj.num = 3;
console.log(obj);  // { num: 3}
console.log(obj2);  // { num: 3}

참조타입은 객체의 속성들이 모여있는 묶음의 주소값을 알고있다.

따라서 obj의 속성들을 복사하기위해 obj2 = obj를 실행한 경우 obj2는 obj의 속성묶음의 주소를 갖게된다.

이 때 원시타입의 복사(깊은 복사)와 차이가 발생한다. obj.num과 obj2.num은 결국 데이터를 받아올 메모리 주소를 같은 공간에서 관리하기 때문에 두 변수 중 하나가 변경되는 경우 나머지 하나도 변경된 값을 가지게 된다.

 

다른 객체에서 값을 복사해오지만, 독립적으로 관리하고 싶은 경우 반복문을 통해 객체 내부를 돌면서 값을 복사해올 수 있다!

 

let obj = {
    num: 1,
};

function copyObj() {
    let newObj = {};

    for(let key in obj) {
        newObj[key] = obj[key];
    }

    return newObj;
}

let obj2 = copyObj();

obj.num = 3;
console.log(obj);  // { num: 3 }
console.log(obj2);  // { num: 1 }

 

하지만 위의 방법을 사용한다면 객체의 속성 중 참조타입이 있는 경우가 문제가 된다. 이를 중첩된 객체라고 한다.

 

 

참조타입의 깊은 복사

중첩된 객체를 대비해 깊은 복사를 수행하려면 재귀적인(recursive) 방법이 필요하다.

 

let otherRef = {
    data1: 1,
    data2: 2,
};

let obj = {
    num: 1,
    refInfo: otherRef,
};


function copyObj(target) {
    let newObj = {};

    if(typeof target === 'object' && target != null) {
        for(let key in target) {
            newObj[key] = copyObj(target[key]);
        }
    } else {
        newObj = target;
    }

    return newObj;
}

let obj2 = copyObj(obj);

obj.refInfo.data1 = 10;
console.log(obj);  // { num: 1, refInfo: { data1: 10, data2: 2 } }
console.log(obj2);  // { num: 1, refInfo: { data1: 1, data2: 2 } }

 

이 방법을 활용하면 복사하고자하는 값의 타입이 참조타입일 때 그 객체의 내부까지 접근하여 복사해올 수 있다.