불변 객체
지난 시간에 배운 내용에 따르면, 자바스크립트의 데이터 타입은 기본형과 참조형으로 분류되며,
기본형 데이터는 불변값, 참조형 데이터는 가변값을 나타냅니다.
이번 시간에 배울 객체는 참조형 데이터에 속하며, 가변의 성질을 지니는데요.
그렇다면 불변 객체는 왜 필요할까요?
아래의 코드는 user 객체를 user2 객체로 복사하여 user2의 name 프로퍼티를 변경하였습니다.
var user = {
name: 'Gaeun',
gender: 'female',
};
var user2 = user;
user2.name = 'Jiwon';
그러나 아래의 그림과 같이 user 객체와 user2 객체는 같은 내부 프로퍼티를 공유하므로, user2의 이름을 변경할 시 기존 user의 이름도 함께 변경됩니다.
이는 참조형 데이터 객체의 가변성 때문인데요.
💡 이 대목에서 불변 객체의 필요성이 대두됩니다.
1-5-1 불변 객체를 만드는 간단한 방법
데이터의 가변은 앞서 본 예시처럼 내부 프로퍼티를 변경할 때만 성립합니다.
그러므로 불변 객체를 만드는 첫 번째 간단한 방법은, 새로운 데이터를 할당하는 것입니다.
var user = {
name: 'Gaeun',
gender: 'female',
};
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
var user2 = changeName(user, 'Jiwon');
그림과 같이 user 객체와 user2는 서로 다른 데이터를 참조하고 있으므로, 각각의 내부 프로퍼티 변경에 영향을 받지 않게 됩니다.
그러나 이 방법의 경우 아래와 같이 객체가 가진 프로퍼티가 많으면, 이를 모두 작성해야 한다는 단점을 지닙니다.
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
age: user.age,
address: user.address,
phoneNumber: user.phoneNumber,
.
.
.
};
};
모든 프로퍼티를 복사하는 함수 만들기
아래의 함수 copyObject
는 모든 프로퍼티를 순회하여 복사하는 함수입니다.
var user = {
name: 'Gaeun',
gender: 'female',
};
var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
};
var user2 = copyObject(user);
user2.name = 'Jiwon';
console.log(user.name, user2.name); // Gaeun Jiwon
console.log(user === user2); // false
그러나 이 함수는 얕은 복사만 수행한다는 한계를 지닙니다.
얕은 복사가 무엇일까요?
1-5-2 얕은 복사와 깊은 복사
다음은 참조형 데이터가 저장된 프로퍼티를 가진 user 객체입니다.
var user = {
name: 'Gaeun',
gender: 'female',
//👇🏻 참조형 데이터가 저장된 프로퍼티
urls: {
github: 'github.com/gaeunnlee',
blog: 'gaeunnlee.tistory.com',
},
};
이 user 객체를 복사하는 경우, 얕은 복사와 깊은 복사는 다음과 같이 이루어집니다.
얕은 복사 | 깊은 복사 |
---|---|
바로 아래 단계의 값만 복사 | 내부의 모든 값을 복사 |
❗따라서 얕은 복사의 경우, 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사시 그 주솟값만 복사합니다.
앞서 본 copyObject
함수는 내부의 모든 값까지 복사해야 한다는 점을 간과하였으므로 얕은 복사입니다.
그러므로 copyObject
를 통하여 user을 복사한 user2의 경우,
프로퍼티 중 참조형 데이터인 urls의 내부 프로퍼티 github을 변경할 시,
기존 user의 github도 함께 변경됩니다.
var user2 = copyObject(user);
user2.urls.github = 'github.com/user2';
console.log(`user github: ${user.urls.github}`); // user github: github.com/user2
console.log(`user2 github: ${user2.urls.github}`); // user2 github: github.com/user2
모든 프로퍼티를 깊게 복사하는 함수 만들기
그렇게 모든 프로퍼티를 깊게 복사하는 함수를 만들면 다음과 같습니다.
copyObjectDeep
함수는 프로퍼티가 참조형 데이터인 경우 copyObjectDeep
함수를 재귀적으로 호출하여 깊은 복사를 수행합니다.
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;
};
JSON을 활용한 깊은 복사
마지막으로, JSON을 활용하여 간단히 깊은 복사를 수행할 수 있는데요.
단, JSON으로 변경할 수 없는 프로퍼티들은 모두 무시하여 복사합니다.
var copyObjectViaJSON = function (target) {
return JSON.parse(JSON.stringify(target))
}
아래의 코드에서 참조형 데이터를 지닌 프로퍼티까지 모두 깊은 복사가 알맞게 수행된 것을 확인할 수 있지만,
JSON으로 변경할 수 없는 프로퍼티인 sayHi
는 무시하여 복사한 것을 확인할 수 있습니다.
var user = {
name: 'Gaeun',
gender: 'female',
urls: {
github: 'github.com/gaeunnlee',
blog: 'gaeunnlee.tistory.com',
},
//👇🏻 JSON으로 변경할 수 없는 프로퍼티
sayHi: () => {
console.log('hi');
},
};
var copyObjectViaJSON = function (target) {
return JSON.parse(JSON.stringify(target));
};
var user2 = copyObjectViaJSON(user);
user2.name = 'Jiwon';
user2.urls.blog = 'user2.blog.com'
user2.urls.github = 'github.com/user2'
console.log(user);
console.log(user2);