JavaScript의 얕은 복사, 깊은 복사
객체의 불변성을 유지,변경 방법에 대해 알아보자!
불변 객체는 내용을 변경할 수 없는 객체를 의미하며, 얕은 복사와 깊은복사로 객체의 불변성을 유지하거나 변경하는 방법을 알아본다.
얕은 복사
얕은 복사는 객체의 1차 속성만을 복사하고, 내부 객체는 원본 객체와 같은 참조를 공유한다.
- 얕은 복사는 객체의 최상위 속성(1차 속성)만 복사한다.
- 내부 객체나 배열과 같은 2차 속성은 원본 객체와 동일한 참조를 가진다.
- 얕은 복사는 객체의 구조가 단순하고, 중첩된 객체가 없거나 중첩 객체를 공유해도 문제가 없을 때 사용하기 적합하다.
- 객체의 깊은 속성(2차 속성 이상)을 변경해야 할 때는 주의가 필요하며 이러한 변경은 원본 객체에도 영향을 미칠 수 있음
1-1) 1차 속성이란?
1차 속성이라고 하니 처음에 어색했는데, 아래의 예시를 보면 이해할 수 있다.
const person = {
name: 'rarrit', // 1차 속성
address: { // 1차 속성
city: 'seoul', // 2차 속성
}
};
const personCopy = { ...person };
personCopy.name = "rarrit change"; // 1차 속성 변경
personCopy.address.city = "incheon"; // 내부 객체의 2차 속성 변경
console.log(person.name); // "rarrit change" (1차 속성은 독립적으로 존재)
console.log(person.address.city); // "incheon" (내부 객체는 공유됨)
1-2) 문법
for-in
문을 사용한 얕은 복사
var copyObject = function (target) {
var result = {
name: 'minkyu',
};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있음.
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면 된다.
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
var user = {
name: 'wonjang',
gender: 'male',
};
// 위 함수를 사용하여 얕은 복사
var user2 = copyObject(user);
user2.name = 'twojang';
console.log(user2.name); // twojang : user2의 name은 변경됌
console.log(user.name); // wonjang : user의 1차 속성 값은 변경되지 않음
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user === user2); // false
spread
연산자를 사용한 얕은 복사
// 객체
const x = {a: 1, b: 2};
const y = { ... x };
console.log(y); // a: 1, b: 2
y.a = 2;
console.log(x.a); // 1
console.log(y.a); // 2
// 배열
const arr = [1,2,3];
const arrCopy = [...arr];
console.log(arrCopy); // [1,2,3]
arrCopy.push(4);
console.log(arr); // [1,2,3]
console.log(arrCopy) // [1,2,3,4]
1-3) 정리
-
유의할 점
- 얕은 복사는 1차 속성만을 복사하며, 중첩된 객체는 원본과 동일한 참조를 가진다.
- 내부 객체의 변경 사항은 원본 객체에도 영향을 미친다.
- 중첩 객체가 많은 경우, 얕은 복사는 원본 객체에 영향을 미칠 확률이 커서 그럴 경우 원치않은 오류들을 만날 수 있음
- 내부 객체의 변경을 원하지 않을 때는 얕은 복사를 사용하면 안된다. (위의 내용 조심!)
-
사용하기 좋은 예
- 객체의 상위 속성만 변경할 때 유용함
깊은 복사
깊은 복사는 객체의 모든 속성(중첩된 객체 포함)을 새롭게 복사하여 원본 객체와 독립된 새로운 객체를 생성함
2-1) 사용 이유
만약 객체의 모든 속성을 새롭게 복사해야 할 경우, 원본 객체가 변경되지 않아야 할 경우에 사용한다. 아래는 얕은 복사를 사용한 예시이다. [확인] 주석을 확인 해보자.
var copyObject = function (target) {
var result = {
name: 'minkyu',
};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있음.
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면 된다.
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
var user = {
name: 'wonjang',
gender: 'male',
infos: {
info1 : 1, // [확인] 변경할 값
infos2 : 2
}
};
// 위 함수를 사용하여 얕은 복사
var user2 = copyObject(user);
user2.infos.info1 = 1000; // [확인] 중첩된 객체를 변경
console.log(user.infos.info1); // 1000
console.log(user2.infos.info1); // 1000
if (user.infos.info1 === user2.infos.info1) {
console.log('중첩된 객체가 동일하게 변경되었습니다.'); // [확인] true
}
console.log(user.infos.info1 === user2.infos.info1); // [확인] true
- 얕은 복사를 할 경우 1차 속성은 변경이 되지만 2차 속성(중첩된 객체)는 동일하게 변경되어, 원본 객체까지 변경이되기에 이럴 경우 깊은 복사를 통해 중첩된 객체까지 복사를 해야한다.
- 만약 프로젝트를 진행하다가 위와 같이 복사 후 값을 변경하면 .. holy moly..!!
2-2) 문법
기존 얕은 복사 후 안쪽 객체까지 다시 얕은 복사 실행
아래의 [1] ~ [n] 을통해 설명하겟다.
// [1] 기존 얕은 복사 함수 생성
var copyObject = function (target) {
var result = {
name: 'minkyu',
};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
// 하드코딩을 하지 않아도 괜찮아요.
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
// 되겠죠!?
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
// 중첩된 객체에 대한 [깊은 복사] 살펴보기
var user = {
name: 'wonjang',
urls: {
portfolio: 'http://github.com/abc',
blog: 'http://blog.com',
facebook: 'http://facebook.com/abc',
}
};
// [2] 1차 copy
var user2 = copyObject(user);
// [3] 2차 copy -> 이렇게까지 안쪽 객체까지 복사를해줘야함
user2.urls = copyObject(user.urls);
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);
user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);
// 결론 : 객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사 ⇒ 재귀적 수행!
JSON.parse(JSON.stringify(original))
이 문법을 사용하면 쉽게 깊은 복사가 가능하다. 아래의 예시를 확인해보자.
const user = {
name: "rarrit",
address: {
city: "seoul",
}
};
const userCopy = JSON.parse(JSON.stringify(user)); // 깊은 복사
userCopy.name = "rarrit2"; // 1차 속성 변경
userCopy.address.city = "soeoul2"; // 내부 객체 속성 변경
console.log(userCopy.name); // "rarrit2"
console.log(userCopy.address.city); // "seoul2" (원본 객체는 변경되지 않음)
-
이해해야 할 점
- 깊은 복사는 모든 중첩된 객체까지 재귀적으로 복사함
- 원본 객체와 복사된 객체는 완전히 독립이다.
-
조심해야 할 부분
- 복사 대상이 크거나 깊이가 깊을 경우, 성능에 영향을 미칠 수 있음.
- JSON.parse(JSON.stringify()) 방법은 함수, undefined, Infinity, NaN 등의 특별한 값들을 처리하지 못함.
마무리
JavaScript의 얕은 복사와 깊은 복사를 알아보며, 어떤 차이가 있는지 알 수 있었다.
1차 속성만 간단하게 변경이 필요할 경우 얕은 복사를 사용하고 객체의 불변성을 유지해야 하는 경우에는 깊은 복사를 사용하도록 한다.