자바스크립트에는 기본형 데이터와 참조형 데이터가 있습니다.
기본형 데이터를 변경하면 데이터는 변하지 않습니다. (불변성) 마찬가지로 참조형 데이터도 데이터 자체를 변경하고자 하면 즉, 새로운 데이터를 할당한다면 기본형 데이터와 같이 기존 데이터는 변경되지 않습니다.
그러나, 참조형 데이터인 객체를 복사해서 그 객체안의 내부 프로퍼티를 변경 할 일이 생긴다면 복사한 객체를 변경한다하더라도 기존의 객체의 프로퍼티는 변하지 않아야 하는 경우가 있습니다.
그러나 자바스크립트에서는 참조형 데이터인 내부 프로퍼티를 수정했을 때는 가변성이라는 성질로 인하여 기존의 객체도 변하게 됩니다.
그럼 객체를 복사해서, 내부프로퍼티를 변경하고 싶지만 원본객체는 유지하고 싶을 때 어떻게 해야 할까요?
바로 불변 객체를 만들면 됩니다.
방법은 간단합니다.
기존 객체의 정보를 복사해서 새로운 객체를 반환하면 됩니다.
var student = {
name : "coder_h",
ban : 3,
gender : 'male'
}
var copyObject = function(target){ // (1)
var result = {};
for(var prop in target){
result[prop] = target[prop];
}
return result;
}
var student2 = copyObject(student);
student2.name = "kim";
console.log(student === student2); // false
console.log(student.name, student2.name); // coder_h kim
(1) copyObject라는 객체를 복사하는 함수가 있습니다.
for in 문법을 이용해서 target 파라미터에 담기는 객체의 프로퍼티를 복사해서 새로운 result라는 객체를 만들어 반환 해주는 함수 입니다.
하지만 언뜻 잘 복사되는 것 같지만 몇가지 문제가 있습니다.
객체는 한번더 객체 프로퍼티를 할당하기 위하여 기본형 데이터와 달리 한단계를 더 거치게 됩니다.
만약 객체안에 프로퍼티로 또 객체가 있는 중첩 객체의 경우를 살펴 봅시다.
var student = {
name : "coder_h",
ban : 3,
gender : 'male',
friend : { // 새로 추가한 프로퍼티 (1)
name : "hello"
}
}
var copyObject = function(target){
var result = {};
for(var prop in target){
result[prop] = target[prop];
}
return result;
}
var student2 = copyObject(student);
student2.name = "kim";
student2.friend.name = "Bye"; // student2의 친구이름을 변경 (2)
console.log(student === student2); // false
console.log(student.name, student2.name);// coder_h kim
console.log(student.friend.name, student2.friend.name); // Bye Bye (3)
(1) student에 새로운 프로퍼티로 friend라는 객체를 할당했습니다.
(2) 복사한 객체의 friend.name 프로퍼티를 변경해줍니다.
(3) 여기서 문제가 생깁니다. 분명 copyObject 함수로 객체를 복사해서 새로운 객체를 반환 해주었다고 알고 있었는데
기존의 객체까지 변경된것을 확인할 수 있습니다.
바로 여기서 확인할 수 있는 것은
중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다.
즉, 복사한 객체에서 해당 프로퍼티에 대해 같은 주소값을 가지고 있다는 의미이기 때문에 복사 객체의 프로퍼티를 변경하면 원복객체까지 영향을 끼치게 되는 것입니다.
이것이 얕은 복사의 한계입니다.
얕은 복사와 깊은 복사에 대해서 한번 알아보겠습니다.
얕은 복사 : 바로 아래 단계의 값만 복사함.
깊은 복사 : 내부의 모든 값들을 하나하나 찾아서 전부 복사함.
저기 위에서 copyObject함수는 얕은 복사의 예시 입니다.
깊은복사 형태로 새로운 함수를 만들어서 비교해보겠습니다.
var student = {
name : "coder_h",
ban : 3,
gender : 'male',
friend : {
name : "hello"
}
}
// 깊은복사 (1)
var copyObjectDeep = function(target){
var result = {};
if(typeof target === 'object' && target !== null){
for(var prop in target){
result[prop] = copyObjectDeep(target[prop]);
}
} else {
result = target;
}
return result;
}
var student2 = copyObjectDeep(student);
student2.name = "kim";
student2.friend.name = "Bye"; // (2)
console.log(student === student2);
console.log(student.name, student2.name);
console.log(student.friend.name, student2.friend.name); // hello Bye (3)
(1) copyObjectDeep 함수 설명
기본형 데이터인 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야 합니다. 즉, 참조형 데이터가 있을때 마다 재귀적으로 수행해야만 비로소 깊은 복사가 되는 형태 입니다.
target이 객체라면 내부 프로퍼티를 순회하면서 함수를 재귀적으로 호출하고**, 객체가 아닌 경우에는 target을 그대로 result에 할당 시켜 반환합니다.
이 함수를 사용하면 중첩객체를 복사하더라도 원본과 복사한 객체는 서로 완전히 다른 객체를 참조하게 되므로 얕은 복사일때와 같은 상황이 벌어지지 않습니다.
(2) 복사한 객체에 friend.name = "Bye"를 새롭게 할당합니다.
(3) hello Bye 결과 값으로 나오듯이 원복 객체에 영향을 주지않고 복사한 객체에 값이 바뀐 것을 확인할 수 있습니다.
이와 같은 간단한 방법으로 불변 객체를 만드는 방법을 확인했습니다. 하지만 불변객체를 만드는 방법은 여러가지가 있습니다. immutable.js, immer.js 등의 라이브러리가 있고, ES6의 spread operator, Object.assign 메서드 등도 같은 목적으로 활용 할수 있습니다. 다음번에 부록으로 이와 같은 방법을 활용하여 불변 객체를 만드는 방법에 대해 알아보는 시간을 갖도록 하겠습니다.
< 출처 : https://velog.io/@hazzang/JS-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4 >