JavaScript에서 객체나 배열을 자주 복사한다. 내가 사용하는 복사가 얕은 복사인지 깊은 복사인지 자세하게 알기 위해 구글링을 통해 답변을 찾았다.
내가 주로 자주 사용하는 복사 방법은 Object.assign({}, obj)
, {...obj}
이 두 방식이 있다.
const obj = {id: 1, test: {name: "hello"} };
const clone1 = Object.assign({}, obj);
clone1.id = 2;
const clone2 = { ...obj };
clone2.id = 3;
console.dir(obj, {depth: null}); // {id: 1, test: {name: "hello"}}
console.dir(clone1, {depth: null}) // {id: 2, test: {name: "hello"}}
console.dir(clone2, {depth: null}) // {id: 3, test: {name: "hello"}}
id 값을 변경할 때 서로 다른 것을 확인할 수 있어 이게 깊은 복사구나 싶었다. 제대로 찾아보기 전까진..
const obj = {id: 1, test: {name: "hello"} };
const clone1 = Object.assign({}, obj);
clone1.test.name = "world";
const clone2 = { ...obj };
clone2.test.name = "new world";
console.dir(obj, {depth: null}); // {id: 1, test: { name: "new world" }}
console.dir(clone1, {depth: null}) // {id: 2, test: { name: "new world" }}
console.dir(clone2, {depth: null}) // {id: 3, test: { name: "new world" }}
위 코드에서 독립적인 형태를 원했다. 하지만 결과는 그렇지 않았다.
객체 안에 객체 또는 배열인 경우 값이 아닌 주소를 참조하는 방식인 듯 했다. 그렇기에 얉다해서 얉은 복사인 것 같다.
그렇다면 깊은 복사는 어떻게 만들까?
JSON.stringif 와 JSON.parse를 이용해서 사용하는 방법이다.
const obj = {id: 1, test: {name: "hello"} };
const clone = JSON.parse(JSON.stringify(obj));
clone.test.name = "world";
console.dir(obj, {depth: null}); // {id: 1, test: {name: "hello"} };
console.dir(clone, {depth: null}}; // {id: 1, test: {name: "world"} };
객체를 문자열로 변환 후, 다시 객체로 변환되었기에 이전 객체에 대한 주소 참조가 없어진다. 그렇기에 깊은 복사가 가능했는데, 문제는 깊은 복사가 불가능한 타입들이 꽤 많다는 단점이 있다. 그 예로 함수, Date 객체, 정규표현식, Infinity 등 데이터가 복사되지 않고 유실되어버린다.
function deepCopy(obj) {
const clone = {};
for(let key in obj) {
typeof obj[key] === "object" && obj[key] !== null
? clone[key] = deepCopy(obj[key])
: clone[key] = obj[key];
}
return clone;
}
const obj = {id: 1, test: {name: "hello"} };
const clone = deepCopy(obj);
clone.test.name = "world";
console.dir(obj, {depth: null}); // {id: 1, test: {name: "hello"} };
console.dir(clone, {depth: null}}; // {id: 1, test: {name: "world"} };
깊게 들어가서 하나하나 복사하는 방법이다. 객체가 깊으면 깊을 수록 시간복잡도가 늘어난다는 단점이 있다.
가장 쉬운 방법은 누군가 만들어 놓은 걸 가져다가 사용하는 방법이다. 이미 Lodash를 사용하고 있으면 더 좋다.
const lodash = require('lodash');
const obj = {id: 1, test: {name: "hello"} };
const clone = lodash.cloneDeep(obj);
clone.test.name = "world";
console.dir(obj, {depth: null}); // {id: 1, test: {name: "hello"} };
console.dir(clone, {depth: null}}; // {id: 1, test: {name: "world"} };
Simple하다. 단점으로는 해당 라이브러리를 사용하고 있지 않다면 고려해봐야한다.