Copy by reference (Shallow Copy)
Shallow Copy
JavaScript에서 다른 원시 자료형(type)과 달리, 객체(Object)는 변수에 Object 자체가 저장되는 것이 아닌, 변수에는 Object의 Reference(주소값)가 저장된다.
let user = {
name: "John",
};
이 때 변수 user에 객체 { name : "John" } 자체가 저장되는 것이 아니라, 객체가 담긴 메모리 위치의 "주소값"이 저장된다는 것이다. (필자는 C, C++ 때의 포인터를 떠올리니 어렵지 않게 이해할 수 있었다.)
따라서 object가 할당된 변수를 복사할 때는 object 자체가 복제되는 것이 아니라 object의 reference가 copy된다.
let user = {
name: "John",
age: 30
};
let admin = user; // reference copy
delete admin.name;
console.log(user);
// output : { age: 30 }
line 6에서 admin에 user에 저장된 객체의 reference가 저장되었기에, admin과 user는 같은 객체를 가리킨다.
따라서, admin에서 key값이 name인 property를 delete하면 user를 출력해 보아도 똑같이 영향이 가는 것을 볼 수 있다.
Reference 비교
객체 비교 시 동등 연산자 ==
와 일치 연산자 ===
는 동일하게 동작한다.
비교 시 피연산자인 두 객체가 동일한 객체일 때 true를 return한다.
let a = {};
let b = a; // copy by reference
console.log( a == b ); // true
console.log( a === b ); // true
변수 b
에 a
에 저장된 객체의 reference가 copy되므로, 두 객체가 동일하여 true를 return.
let a = {};
let b = {};
console.log( a == b ); // false
console.log( a === b ); // false
반대로 이런 경우, 자칫 a === b
가 true를 return한다고 생각할 수 있다.
Object의 property가 모두 동일할지라도, a와 b에 저장된 객체의 reference가 다르므로(근본적으로, a와 b가 참조하는 객체는 다른 객체이므로) a === b
는 false를 return한다.
copy by value(Deep Copy)?
copy by value(Deep Copy)
그렇다면 객체 자체를 복제(copy by value) 시켜 같은 property를 가진 새로운 객체를 생성하려면 어떻게 해야 할까?
직접 모든 property를 순회하여 copy하는 방법이 있다.
let origin = {
key1: 3,
key2: 5,
key3: 7,
};
let replica = {};
for (let key in origin) {
replica[key] = origin[key];
}
console.log(replica);
// output : { key1: 3, key2: 5, key3: 7 }
Object.assign()
또는, Object.assign()
메소드를 이용하여 복제하는 방법이 있다.
Object.assign()
method는, 출처 object의 모든 property를 대상(목표) 객체에 복사한다.
이를 이용하면, 여러 object의 모든 property를 병합한 새로운 object를 생성할 수도 있다.
Object.assign("target", "source1", "source2", ...)
와 같이 사용.
let origin1 = { key1: 3 };
let origin2 = { key2: 5 };
let origin3 = { key3: 7 };
let replica = { key1: 100 };
Object.assign(replica, origin1, origin2, origin3);
console.log(replica);
// output : { key1: 3, key2: 5, key3: 7 }
목표 object에 동일한 key값을 가진 property가 존재하면, 기존의 value가 덮어씌워진다. (key1)
아래와 같이 새로운 변수에 copy한 object의 reference를 할당할 수도 있다.
let origin = {
key1: 3,
key2: 5,
key3: 7,
};
let replica = Object.assign({}, origin);
console.log(replica);
// output : { key1: 3, key2: 5, key3: 7 }
다차원 객체(중첩 객체)의 Deep Copy
그러나, Object.assign()도 완전한 해결책이 되어주지는 못한다.
Object property의 value가 다른 object의 reference인 경우도 존재한다.
let origin = {
key1: 3,
key2: { key2_1: 5, key2_2: 7, key2_3: 9 },
};
let replica = Object.assign({}, origin);
delete replica.key1;
delete replica.key2.key2_1;
console.log(replica);
console.log(origin);
// output :
// { key2: { key2_2: 7, key2_3: 9 } }
// { key1: 3, key2: { key2_2: 7, key2_3: 9 } }
line 10에서 Object.assign을 통해 객체를 복사했다.
key1은 당연히 값 자체가 복사되었지만, key2에서는 value가 객체의 reference이기 때문에, 객체의 reference가 origin과 replica가 { key2_1: 5, key2_2: 7, key2_3: 9 }
를 공유한다.
따라서 line 13 - replica에서 delete를 실행한 결과 origin 객체를 출력해도 key2_1 property가 delete된 것을 확인할 수 있다.
이러한 다차원 객체를 deep copy하기 위해서는 몇 가지 방법이 존재한다.
1. JSON 객체의 JSON.stringify()
, JSON.parse()
메소드 이용
JSON.stringify()
메소드를 이용하여 객체를 문자열로 형변환하고,
다시 JSON.parse()
메소드를 이용해 문자열을 객체로 parsing한다.
이 방법의 단점은, 객체의 property로 함수가 존재한다면, JSON.stringify()
메소드가 undefined
로 처리한다는 점이다.
2. 직접 함수를 만들어 deep copy (재귀)
copy하려는 객체의 모든 property의 각 value를 조사하여, 그 값이 객체인 경우 그 객체의 구조를 모두 복사하는 함수를 직접 만들어 사용한다.
3. lodash 모듈의 cloneDeep() 메소드 이용
$ npm i lodash
로 lodash 모듈을 설치하면, cloneDeep() 메소드를 사용할 수 있다.
const lodash = require("lodash");
let origin = {
key1: 1,
key2: {
key2_1: 2,
key2_2: 3,
},
func: function () {
return this.key1;
},
};
let replica = lodash.cloneDeep(origin);
console.log(replica);
// output : { key1: 1, key2: { key2_1: 2, key2_2: 3 }, func: [Function: func] }