특성 | 얕은 복사 (Shallow Copy) | 깊은 복사 (Deep Copy) |
---|---|---|
복사 범위 | 최상위 수준의 속성만 복사 | 모든 수준의 속성을 복사 |
중첩된 객체/배열 | 원본과 복사본이 중첩된 객체/배열을 공유 | 중첩된 객체/배열도 복사하여 완전히 독립적 |
메모리 참조 | 원본과 복사본이 중첩된 객체/배열의 메모리 참조를 공유 | 원본과 복사본이 서로 다른 메모리 참조를 가짐 |
변경 시 영향 | 중첩된 객체/배열을 변경하면 원본과 복사본 모두에 영향 | 복사본에서의 변경이 원본에 영향을 미치지 않음 |
사용 방법 예 | Array.prototype.slice(), Object.assign({}, obj), 스프레드 연산자 | 재귀 함수, JSON.parse(JSON.stringify(obj)), 라이브러리(예: lodash의 _.cloneDeep(obj)) |
적용 시나리오 | 간단한 데이터 구조, 중첩된 구조가 없거나 공유해도 되는 경우 | 복잡한 데이터 구조, 완전히 독립적인 복사본이 필요한 경우 |
{ a: 1, b: { c: 2 } }
를 얕은 복사하면, b
속성 내의 객체는 원본과 복사본 간에 공유된다.// 원본 객체 생성
const original = {
a: 1,
b: 'string',
c: true
};
// 얕은 복사 수행
const shallowCopy = { ...original };
// 복사본 수정
shallowCopy.a = 2;
shallowCopy.b = 'modified';
shallowCopy.c = false;
// 결과 출력
console.log(original); // { a: 1, b: 'string', c: true }
console.log(shallowCopy); // { a: 2, b: 'modified', c: false }
중첩된 구조가 없다면 일반적으로 생각하는 원본과 다른 또 하나의 복사본이 생성된다.
그러나 배열이나 객체가 중첩된 구조를 가진다면 얕은 복사의 주의 사항이 드러난다.
// 원본 객체에 중첩된 객체 포함
const original = {
a: 1,
b: {
c: 2,
d: 3
}
};
// 얕은 복사 수행
const shallowCopy = { ...original };
// 복사본의 중첩된 객체 수정
shallowCopy.b.c = 20;
// 결과 출력
console.log(original); // { a: 1, b: { c: 20, d: 3 } }
console.log(shallowCopy); // { a: 1, b: { c: 20, d: 3 } }
이와 같이 중첩이 존재한다면 얕은 복제는 원본의 참조 주소만 복사하므로 복사본을 수정했을 때 원본이 수정되면서 불변성을 유지할 수 없게 된다.
// 원본 배열
const originalArray = [1, 2, 3, 4];
// slice()를 사용한 얕은 복사
const shallowCopiedArray = originalArray.slice();
// 복사본 수정
shallowCopiedArray.push(5);
console.log(originalArray); // [1, 2, 3, 4]
console.log(shallowCopiedArray); // [1, 2, 3, 4, 5]
// 원본 객체
const originalObject = { a: 1, b: { c: 2 } };
// Object.assign()을 사용한 얕은 복사
const shallowCopiedObject = Object.assign({}, originalObject);
// 복사본의 중첩된 객체 수정
shallowCopiedObject.b.c = 20;
console.log(originalObject); // { a: 1, b: { c: 20 } }
console.log(shallowCopiedObject); // { a: 1, b: { c: 20 } }
복사를 하려는 배열이나 객체가 중첩된 구조를 가지고 있는 지 판단하여 얕은 복사와 깊은 복사 중 무엇을 할 지 판단해야 한다.
{ a: 1, b: { c: 2 } }
를 깊은 복사하면, b
속성 내의 객체도 새롭게 복사되어 원본과는 독립적인 상태가 된다.const original = {
a: 1,
b: {
c: 2,
d: [3, 4],
e: new Date()
}
};
const deepCopy = JSON.parse(JSON.stringify(original));
// 복사본을 수정하여도 원본에 영향을 주지 않음
deepCopy.b.c = 20;
deepCopy.b.d.push(5);
console.log(original); // { a: 1, b: { c: 2, d: [3, 4], e: Date 객체 } }
console.log(deepCopy); // { a: 1, b: { c: 20, d: [3, 4, 5], e: 문자열화된 Date 객체 } }
Date 객체
, undefined
, 심볼
, 순환 참조
등은 올바르게 복사가 되지 않는다.JSON.stringify()
)하면서 중첩된 데이터도 문자열로 변환되므로 메모리 주소나 참조는 포함되지 않는다. 다시 역직렬화(JSON.parse()
)하면 중첩 구조의 데이터도 같이 문자열에서 데이터로 변환되는 것이다.function deepCopy(obj, hash = new WeakMap()) {
if (obj === null) return null; // null은 그대로 반환
if (typeof obj !== "object") return obj; // 기본 타입은 그대로 반환
if (obj instanceof Date) return new Date(obj); // Date 객체 복사
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags); // RegExp 객체 복사
if (hash.has(obj)) return hash.get(obj); // 순환 참조 처리
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
for (const key of Object.keys(obj)) {
result[key] = deepCopy(obj[key], hash); // 재귀 호출
}
return result;
}
const original = {
a: 1,
b: {
c: 2,
d: [3, 4]
},
e: new Date(),
f: function() { console.log("f()"); },
g: new RegExp("\\w+")
};
const deepCopyResult = deepCopy(original);
console.log(original);
console.log(deepCopyResult);
# lodash 라이브러리 설치
npm install lodash
// lodash 라이브러리 불러오기
const _ = require('lodash');
// 깊은 복사할 객체
const original = {
a: 1,
b: {
c: 2,
d: [3, 4],
e: { f: 5 }
}
};
// _.cloneDeep()을 사용한 깊은 복사
const deepCopy = _.cloneDeep(original);
// 복사본 수정
deepCopy.b.c = 20;
deepCopy.b.d.push(5);
// 결과 출력
console.log(original); // { a: 1, b: { c: 2, d: [3, 4], e: { f: 5 } } }
console.log(deepCopy); // { a: 1, b: { c: 20, d: [3, 4, 5], e: { f: 5 } } }
Immutable.js
Ramda
자바스크립트의 데이터 구조는 두 가지가 있습니다.
원시타입과 참조 타입입니다.
원시 타입은 변수에 값이 직접 저장됩니다.
참조 타입은 값이 저장된 메모리의 주소를 저장합니다. 주소를 참조라고 부르기 때문에 참조를 변수에 저장하므로 참조 타입이라고 합니다.
이런 구조 때문에 참조 타입에는 깊은 복사와 얕은 복사의 구분이 생깁니다.
참조 타입 예시로 객체를 활용하겠습니다.
깊은 복사는 객체의 모든 레벨을 새롭게 복사합니다. 참조가 가리키는 메모리를 따라가서 전부 복사합니다. 객체 안에 객체가 중첩된 형태를 생각할 수 있습니다. 참조를 따라 메모리 깊숙히 들어가서 값을 가져오는 이미지입니다.
얕은 복사는 최상위 레벨의 속성만 복사합니다.
참조만 복사하게 됩니다.
그 결과 얕은 복사로 만들어진 변수와 원본 변수가 하나의 메모리를 참조하는 상태가 됩니다.
이제 복사한 변수를 수정하면 원본의 데이터가 수정되어 처음 선언한 변수의 값도 같이 변경되는 일이 발생합니다.
이 개념을 알고 자신이 다루는 변수의 타입을 인지하고 필요에 따라 복사하는 방법을 선택할 수 있어야겠습니다.
깊은 복사와 얕은 복사를 수행하는 다양한 방법에 대해 배웠습니다!
감사합니다~ :)