중첩 구조를 가진 객체를 복사할 때,
(e.g. 배열 안에 배열, 배열 안에 객체, 객체 안에 배열, 객체 안에 객체)
const obj = { inObj: { x: 1 } };
const shallowCopied = { ...obj }; // { inObj: { x: 1 } }
console.log(obj === shallowCopied); // false
console.log(obj.inObj === shallowCopied.inObj); // true
import _ from 'lodash'; // lodash의 cloneDeep 이용
const obj = { inObj: { x: 1 } };
const deepCopied = _.cloneDeep(obj); // { inObj: { x: 1 } }
console.log(obj === deepCopied); // false
console.log(obj.inObj === deepCopied.inObj) // false
얕은 복사와 깊은 복사로 생성된 객체는 원본과 다른 객체이다.
즉, 원본 객체와 복사본 객체은 다른 참조값을 가지고 있는 별개의 객체이다.
obj === shallowCopied
➡️ false
obj === deepCopied
➡️ false
하지만 이때,
얕은 복사는 객체에 중첩되어있는 객체의 경우, 참조값을 복사하고,
깊은 복사는 객체에 중첩되어있는 객체까지 모두 복사하여 원시값처럼 완전한 복사본을 만든다.
obj.inObj === shallowCopied.inObj
➡️ true
obj.inObj === deepCopied.inObj
➡️ false
...
const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const shallowCopied = { ...obj };
shallowCopied.inStr = 'DEF'; // 원시자료형 - 복사본이므로 원본 객체에 영향 X
shallowCopied.inObj.x = 2; // 참조자료형 - 같은 주소를 참조하므로 원본 객체에 영향 O
console.log(obj); // { inStr: 'ABC' , inObj: { x: 2 } }
console.log(shallowCopied); // { inStr: 'DEF' , inObj: { x: 2 } }
Object.assign()
const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const shallowCopied = Object.assign({}, obj);
// 위와 동일
slice()
const arr = [1, 2, 3, ['a', 'b', 'c'] ];
const shallowCopied = arr.slice();
shallowCopied.push(4); // 원본 배열에 영향 X
shallowCopied[3].push('d'); // 원본 배열에 영향 O
console.log(arr); // [1, 2, 3, ['a', 'b', 'c', 'd']]
console.log(shallowCopied); // [1, 2, 3, ['a', 'b', 'c', 'd'], 4]
Array.from()
const arr = [1, 2, 3, ['a', 'b', 'c'] ];
const shallowCopied = Array.from(arr);
// 위와 동일
➡️ 만약 중첩된 객체/배열을 복사하고 값을 변경할 경우, 예기치 못하게 원본 값을 변경시킬 수 있으므로, 얕은 복사는 중첩이 없고 값으로 원시자료형만 가지고 있을 때 사용하는 게 좋다.
JSON.parse/stringify
복제하고자 하는 객체를 직렬화하고 다시 JavaScript 객체로 만들면, 깊은 복사를 할 수 있다. 하지만 직렬화할 수 없는 속성은 복제할 수 없다는 제약이 있다.
const obj = { inStr: 'ABC' , inObj: { x: 1 } };
const deepCopied = JSON.parse(JSON.stringify(obj));
deepCopied.inStr = 'DEF'
deepCopied.inObj.x = 2;
console.log(obj); // { inStr: 'ABC' , inObj: { x: 1 } }
console.log(deepCopied); // { inStr: 'DEF' , inObj: { x: 2 } }
cloneDeep()
Lodash는 여러 유틸리티 기능을 제공하는 라이브러리로, Lodash의 cloneDeep()
함수를 사용하면 깊은 복사를 할 수 있다.
clone()
메소드는 얕은 복사를 할 수 있다.import { clone, cloneDeep } from "lodash";
const arr = [1, 2, 3, ['a', 'b', 'c']];
const shallowCopied = clone(arr);
const deepCopied = cloneDeep(arr);
console.log(arr[3] === shallowCopied[3]); // true
console.log(arr[3] === deepCopied[3]); // false
재귀와 반복문을 이용해 직접 깊은 복사를 수행하는 함수를 만들 수 있다.
const deepCopy = (input) => {
const output = Array.isArray(input) ? [] : {};
let key;
let value;
if (typeof input !== 'object' || input === null) {
return input;
}
for (key in input) {
value = input[key];
output[key] = deepCopy(value);
}
return output;
};
rfdc(Really Fast Deep Clone) 라이브러리의 clone()
함수를 이용해 깊은 복사를 할 수 있다.
lodash보다 약 400% 빠른 깊은 복사를 수행한다.
const clone = require('rfdc')();
const arr = [1, 2, 3, ['a', 'b', 'c']];
const deepCopied = clone(arr);
복사 알고리즘의 성능 비교
=
로 재할당slice()
- rfdc의
clone()
/ 반복문 또는 재귀를 이용한 직접 만든 함수JSON.parse()
- lodash의
_.cloneDeep()
- 우선 얕은 복사가 깊은 복사보다 빠르므로, 원시자료형으로만 이루어진 배열/객체라면 얕은 복사를 사용한다.
- 깊은 복사가 필요한 경우에는 rfdc 라이브러리를 사용하는 것이 가장 빠르다.
이 글은 아래 링크를 참고하여 작성한 글입니다.
A Deep Dive into Shallow Copy and Deep Copy in JavaScript
얕은복사 깊은복사를 얕게만 알고 있었는데 이렇게 자세히 설명해주시다니.. 정리도 깔끔하네요 가끔 헷깔리면 이글보면 될꺼같아요 !!