불변성: 기본형 데이터에 해당한다. 변수의 데이터를 바꾸는 방법은 데이터를 변경하는 것이 아니라, 데이터를 새로 만들어 할당하는 동작을 통해 발생한다. 만들어진 데이터는 가비지컬렉팅을 당하기 전까지 변하지 않고 존재한다.
가변성: 참조형 데이터의 기본성질에 해당하나, 방법에 따라 불변성을 유지
하게 할 수 있다. (*
Object.defineProperty, Object.freeze)
기본형 데이터와의 차이는 객체의 변수(프로퍼티)영역
이 별도로 존재한다는 점이다.
(중첩객체를 예로들었을 때 아래와 같이 동작한다.)
즉, 변수영역의 변수는 동일한 주솟값을 갖고있지만 참조하고있는 객체변수영역의 주소값이 변경되면서 가변성을 띄게 된다. mmutable
(가변적인)값이라고 할 수 있다.
1) 객체의 프로퍼티를 변경하는 경우
var a = 10
var b = a
b=30
a === b //false : a와 b는 서로 다른 주솟값을 갖게 되므로 둘은 다르다.
var obj1 = {c:10, d: 'hi'}
var obj2 = obj1
obj2.c=30
obj1 === obj2 //true : obj1과 obj2는 동일한 주솟값을 바라보므로 수정 후에도 동일하다.
2) 객체 자체를 새로운 객체로 변경한 경우
var obj1 = {c:10, d:'hi'}
var obj2 = obj1
obj2 = {c:30, d:'hi'}
obj1 === obj2 //false : 데이터 영역에 새로운 객체를 저장하는 주소영역이 생성되고
//obj2의 주소값이 새로운 주소로 변경된다.
결론: 객체의 가변성은 참조형 데이터를 새로운 데이터로 인식시키는 경우에는 해당되지 않고
내부 프로퍼티를 변경할 때 성립한다.
작업 중, 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 있다. 데이터의 불변성을 확보해야 하는 상황이므로 새로운 객체를 만들어 재할당되도록 한다.
얕은 복사 fn: copyObject
: 중첩객체를 예로 들면 바로 아래단계의 키, 밸류들을 복사하는 것이 얕은 복사이다.
: 얕은 복사에서는 중첩 객체 내부까지는 복사하지 못한다.
: 중첩 객체 내부는 복사한 대상의 주솟값만 복사한 상태
=> 원시값이 변경되면 사본이 변경되고, 사본이 변경되면 원시값도 바뀌게 된다.
깊은 복사 fn: copyObjectDeep
, JSON
: 깊은 복사는 중첩된 객체의 내부까지 복사하는 것을 말한다.
var menu = {
name: "bibimbap",
recipe: {
ingredient: "rice, eggs, etc...",
source: "kochujang",
},
};
function copyObject(obj) {
var result = {};
for (var key in obj) {
result[key] = obj[key];
}
return result;
}
var menu2 = copyObject(menu)
/* (참고)
function copyObject(obj){
return { ... obj}
}
위와 같이 새로운 객체를 반환하는 식으로도 구현가능한데,
얕은 복사에서만 유효하고, 중첩객체가 있는 경우 내부 프로퍼티를 인식하지 못해
undefined를 반환하는 에러가 있다.
*/
1) 얕은복사 - 객체내부 속성을 변경한 경우
menu.name = 'bulgogi'
menu.name === menu2.name //false
2) 얕은복사 - 중첩객체 내부 속성을 변경한 경우
menu2.recipe.source = 'chilli source'
menu.recipe.source === menu2.recipe.source // true
: 사본과 원본 모두 변경되었다. 내부 객체는 복사한 객체의 주소값을 참조하고 있는 상태이기 때문이다. 중첩 객체의 내부까지 깊은 복사로 만들어 주려면 내부프로퍼티에 대하여 copyObject
를 한번 더 진행한다.
menu2.recipe = copyObject(menu.recipe)
menu2.recipe.source = 'chilli source'
menu.recipe.source === menu2.recipe.source // false
3-1) 깊은 복사
내부 객체까지 반영하여 deepcopy
를 하는 함수는 다음과 같이 구현할 수 있다.
function copyObjectDeep(target){
var result ={}
if(typeof target === 'object' && target !==null){
//null의 타입이 'object'이기 때문에 조건을 추가하였다.
for(var prop in target){
result[prop] = copyObjectDeep(target[prop])
//target이 객체일 때, 한번더 내부 프로퍼티를 탐색함
}
} else {
result = target
}
return result;
}
var menu = {
name: "bibimbap",
recipe: {
ingredient: "rice, eggs, etc...",
source: "kochujang",
},
};
var menu2 = copyObjectDeep(menu)
menu2.recipe.source = '비빔면양념'
menu.recipe.source === menu2.recipe.source //false
*
타겟 속성을 갖는 객체를 생성하는 Object.assign, Object.create 역시 얕은 복사를 한다.
const target = { a: 1, b:{c: 3, d: 4}};
const test = Object.create(target)
test.a='바보'
test.a === target.a //false
target.b.c = 100
test.b.c === target.b.c //true : 중첩객체내부 복사하지 못함
3-2) 깊은 복사 - JSON
target 객체를 문자화했다가 다시 객체화 시킨다.
var copyObjectViaJSON = function(target){
return JSON.parse(JSON.stringify(target))
}
const target = { a: 1, b:{c: 3, d: 4}};
var test = copyObjectViaJSON(target)
target.b.c='안뇽'
target.b.c === test.b.c //false
마무리 & 주의하기🧧
참조형 데이터를 수정해야 하는 경우 원본 데이터까지 변경되지 않도록 주의해야하는 경우가 있다.
이럴 때, 원본을 복사한 데이터를 수정대상으로 삼아야 하는데
만일 데이터의 깊이가 깊지 않다면 Object.assign이나 create 를 통해 간단하게 복사해 사용할 수 있지만
내부에 또 참조형 데이터가 있다면 꼭 깊은 복사를 해야한다.