자바스크립트 복사에는 두 가지 깊이가 있다.
얕은 복사는 객체나 배열의 최상위 엔트리만 복사한다. 나머지 하위 엔트리 값은 원본과 동일한 주소 값을 가진다.
깊은 복사는 모든 엔트리를 복사한다. 즉, 트리의 모든 노드 전부 새롭게 생성된 값이다.
const copyObj = {...originObj};
const copyArr = [...originArr];
class Class {}
const origin = new Class();
const copy = {...origin};
copy instanceof Class; // false
그래서 클래스로 생성한 인스턴스를 복사하려면
origin 과 같은 프로토타입을 설정해 줘야 한다.
class Class {}
const origin = new Class();
const copy = {
__proto__ : Object.getPrototypeOf(origin),
...origin
};
copy instanceof Class; // true
const arr = ['a', 'b'];
arr.hasOwnProperty('length'); // true
const copy = {...arr};
copy.hasOwnProperty('length'); // false
배열의 속성인 .length 는 enumerable 하지 않다.
그래서 객체에 배열을 전개 구문으로 복사를 하면 .length 는 복사 되지 않는다.
const copy1 = {...origin}
const copy2 = Object.assign({}, origin};
Object.assign() 은 전개 구문과 거의 똑같이 작동하지만
속성을 생성하는 부분에서 전개구문과 다르다.
Object.assign() 은 getter 와 setter 를 호출한다.
그래서 속성을 단순히 복사하거나 새롭게 정의하는 것이 아니라 할당을 한다.
전개 구문은 복사를 할 때 새로운 속성을 정의한다
const origin = {['__proto__'] : null};
const copy1 = {...origin}
const copy2 = Object.assign({}, origin};
console.log(copy1) // {'__proto__': null}
console.log(copy2) // {}
Object.getPrototypeOf(copy1) // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
Object.getPrototypeOf(copy2) // null
function copyAllOwnProperties(original) {
return Object.defineProperties(
{}, Object.getOwnPropertyDescriptors(original));
}
Object.getOwnPropertyDescriptor() 메서드로 원본 속성의
속성 설명자를 가져온 뒤
Object.defineProperty() 로 새로운 속성을 수정하거나 추가하면
non-enumerable 한 속성가지 복사 할 수 있다.
const arr = ['a', 'b'];
const copy = copyAllOwnProperties(arr);
console.log(copy) // {0: "a", 1: "b", length: 2}
copy.length // 2
const original = {name: 'Jane', score: {history: 'A'}};
const copy = {name: original.name, score: {...original.score}};
copy.score.history = 'B'
console.log(original.score.history !== copy.score.history)
// true
nested 된 곳까지 전개 구문으로 복사 하는 방법
function jsonDeepCopy(original) {
return JSON.parse(JSON.stringify(original));
}
const original = {name: 'Jane', score: {history: 'A'}};
const copy = jsonDeepCopy(original);
JSON 문자열로 변환하고 다시 파싱해서 복사 하는 방법
중요한 단점으로 JSON이 지원하지 않는 키와 값은 복사가 안된다.
const copy = jsonDeepCopy({
[Symbol('a')]: 'abc',
// 심볼은 키로 지원하지 않는다.
b: function () {},
// 지원하지 않는 값
c: undefined,
// 지원하지 않는 값
})
console.log(copy) // {}
function deepCopy(original) {
if (Array.isArray(original)) {
return original.map(elem => deepCopy(elem));
} else if (typeof original === 'object' && original !== null) {
return Object.fromEntries(
Object.entries(original)
.map(([k, v]) => [k, deepCopy(v)]));
} else {
return original;
}
}
original이 객체이거나, 배열이면 재귀함수를 호출한다.
origianl이 원시값이면 original을 리턴한다.
하지만 이 방법은 프로토 타입을 복사 하지 않고,
non-enumerable한 속성을 무시한다.