객체, 즉 참조형 변수를 복사하는 것에 대한 이해
예시)
let var1 = 7;
let var2 = var1;
var2 = 5;
console.log(var1 === var2 ? "var1 === var2" : "var1 !== var2");
console.log("var1 = ", var1, " var2 = ", var2);
결과는
var1 !== var2
var1 = 7 var2 = 5
따라서 기본형 변수를 복사해서 사용하다가 어느 하나의 데이터 값을 바꾸더라도, 복사한 변수나 원본 변수는 서로 독립적이라고 볼 수 있다.
복사본의 데이터 값을 바꾸었을 때 원본의 데이터 값도 바뀌게 된다.
그 이유는, 객체가 복사될 때 참조하는 변수들의 주소를 복사하게 되는데,
그 참조하는 변수들의 데이터를 수정하게 되면 원본이든 복사본이든 같은 변수를 참조하고 있으니 같이 변하게 되는 것이다.
예시)
const obj1 = {
prop1: "value1",
prop2: "value2",
};
let obj2 = obj1;
obj2.prop1 = "new_value1";
console.log(obj1 === obj2 ? "obj1 === obj2" : "obj1 !== obj2");
console.log("obj1's prop1: ", obj1.prop1);
console.log("obj2's prop1: ", obj2.prop1);
이 경우 콘솔에 찍히는 로그는 다음과 같다.
obj1 === obj2
obj1's prop1: new_value1
obj2's prop1: new_value1
obj1 이라는 객체를 그대로 obj2로 복사했기 때문에 코드 상으로는 obj2의 prop1 값만 바꾸었는데도, obj1의 prop1 값도 같이 바뀐 것을 확인할 수 있다.
따라서 다음과 같이 객체 자체를 복사하는 것이 아니라, 객체를 새로 만들고, 원본 객체의 속성을 하나하나 똑같이 붙여주는 함수를 만들어서 사용한다면, 객체의 내용은 똑같이 복사하되, 복사한 객체는 원본 객체와 독립적인 객체로서 활용할 수 있게된다.
function copyObject(target) {
let result = {};
for (property in target) {
result[property] = target[property];
}
return result;
}
테스트를 해보자.
let obj2 = copyObject(obj1);
console.log(obj1 === obj2 ? "obj1 === obj2" : "obj1 !== obj2");
결과는?
obj1 !== obj2
놀랍게도, 단지 복사만 했을 뿐 아직 obj2 의 데이터 값을 변경하지도 않았는데도, 두 객체가 같지 않다는 결과가 나왔다. 여기서는 obj2 가 obj1 이랑 속성, 즉 참조 변수의 이름만 같을 뿐이고 본인의 참조 변수를 obj1 과는 다른 주소에다가 새로 저장했기 때문이다.
그렇다면, 정말 독립적인 객체가 되었는지 값을 변경해보고 확인해보자.
obj2.prop1 = "new_value1";
console.log(obj1 === obj2 ? "obj1 === obj2" : "obj1 !== obj2");
console.log("obj1's prop1: ", obj1.prop1);
console.log("obj2's prop1: ", obj2.prop1);
obj1 !== obj2
obj1's prop1: value1
obj2's prop1: new_value1
이미 위에서 예상한대로 obj2 의 값만 바뀌었고 obj1 의 값은 그대로인 것을 확인 할 수있다.
그렇다면, 객체가 중첩 객체, 즉 객체 안에 속성이 객체인 경우에도 독립적인 복사가 가능할까?
예시)
const obj1 = {
prop1: "value1",
prop2: {
innerProp1: "innerValue1",
innerProp2: "innerValue2",
},
};
let obj2 = copyObject(obj1);
console.log(obj1 === obj2 ? "obj1 === obj2" : "obj1 !== obj2");
결과는 : obj1 !== obj2
이렇게만 보면 서로 독립적인 것 같이 보인다.
그런데, 정말 과연 그럴까?
값을 바꿔보자.
let obj2 = copyObject(obj1);
obj2.prop2.innerProp1 = "newInnerVal1";
console.log("obj1's prop2: ", obj1.prop2);
console.log("obj2's prop2: ", obj2.prop2);
obj1's prop2: { innerProp1: 'newInnerVal1', innerProp2: 'innerVal2' }
obj2's prop2: { innerProp1: 'newInnerVal1', innerProp2: 'innerVal2' }
prop2의 innerProp1 값이 obj2 뿐만 아니라 obj1 까지 바뀐 것을 볼 수 있다.
이는, 위에서 만든 copyObject 함수가 중첩 객체를 고려하지 않고 만들었기 때문이다. 따라서 1-depth 까지만, 즉 중첩 객체가 아닌 객체에 한해서만 사용할 수 있는 함수이다.
그렇다면, 중첩 객체를 복사하여 원본을 건드리지 않고 독립적으로 값을 바꾸려면 어떻게 복사를 해야할까?
이는 사실 간단하다.
이미 copyObject() 함수를 만들었으니 이를 활용하여 재귀적으로(recursively) 탐색하고 잘 빠져나오도록 하기만 하면 된다.
결론부터 보여주면, 다음과 같다.
함수의 동작 논리는 주석으로 자세히 써놓았다.
function copyObjectDeep(target) {
let result = {};
// 경우1: 탐색한 속성의 타입이 객체이고 nonempty 일 때
if (typeof target === "object" && target !== null) {
// 그 객체로서의 속성 안의 inner 속성을 하나하나 탐색해서
for (prop in target) {
// 복사할 곳(result)에 집어넣습니다.
// 이때 함수 자신을 사용하여 재귀적으로 탐색합니다.
result[prop] = copyObjectDeep(target[prop]);
}
}
// 경우2: 탐색한 속성의 타입이 객체가 아닌 기본형 또는 empty 객체일 때
else {
// 그냥 기본형 변수를 그대로 저장합니다.
result = target;
}
// if-else 문을 마치고 나온 결과인 result를 반환합니다.
return result;
}
그럼 이 함수를 사용해서 복사해보고, 이전의 copyObject() 함수의 결과와 비교해보자.
let obj2 = copyObjectDeep(obj1);
console.log(obj1 === obj2 ? "obj1 === obj2" : "obj1 !== obj2");
결과는 : obj1 !== obj2
여기까진 다를 바가 없어 보인다. 이제 값도 바꿔보자.
let obj2 = copyObjectDeep(obj1);
obj2.prop2.innerProp1 = "newInnerVal1";
console.log("obj1's prop2: ", obj1.prop2);
console.log("obj2's prop2: ", obj2.prop2);
obj1's prop2: { innerProp1: 'innerVal1', innerProp2: 'innerVal2' }
obj2's prop2: { innerProp1: 'newInnerVal1', innerProp2: 'innerVal2' }
prop2의 innerProp1 값이 obj2 만 바뀌었고, obj1 는 그대로인 것을 확인할 수 있다.
이 copyObjectDeep() 함수는 임시방편으로 2-depth 까지만 독립 복사가 되도록 만든 것이 아니라 재귀적 방법을 사용하였기 때문에, n-depth 의 중첩 객체도 독립 복사가 가능하다.