딥 자바스크립트 copying

강정환·2021년 8월 22일
0

얕은 복사 vs 깊은 복사

자바스크립트 복사에는 두 가지 깊이가 있다.

  • 얕은 복사는 객체나 배열의 최상위 엔트리만 복사한다. 나머지 하위 엔트리 값은 원본과 동일한 주소 값을 가진다.

  • 깊은 복사는 모든 엔트리를 복사한다. 즉, 트리의 모든 노드 전부 새롭게 생성된 값이다.


얕은 복사하는 방법

전개 구문

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
  • 전개 구문은 enumerable 한 속성만 복사된다.
const arr = ['a', 'b'];
arr.hasOwnProperty('length'); // true

const copy = {...arr};
copy.hasOwnProperty('length'); // false
배열의 속성인 .length 는 enumerable 하지 않다. 
그래서 객체에 배열을 전개 구문으로 복사를 하면 .length 는 복사 되지 않는다.
	

Object.assign()

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

Object.getOwnPropertyDescriptors()

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 된 곳까지 전개 구문으로 복사 하는 방법

JSON

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한 속성을 무시한다.

0개의 댓글