[JavaScript] 객체 복제(복사) Object Cloning

참고 문서

개요

JavaScript의 객체 복제에 대한 정리글.

객체 복제(object cloning)보단 객체 복사(object copying)란 말이 더 흔히 쓰이지만 더 맞는 표현인 복제로 명시함.

얕은 복제 Shallow Cloning

객체 안의 다른 객체가 원본 그대로 유지되는 복제는 얕은 복제로 분류한다.

var child = {
  text: 'Hello World!'
};
var parent = {
  c: child
};
var newObj = {...parent};

newObj === parent // false: 새 인스턴스가 만들어 졌지만
newObj.c === parent.c // true: 프로퍼티가 가리키는 객체까지 복제된 건 아님

전개 구문

얕은 복제 중에는 가장 간단한 방법이다. 우측의 객체(혹은 프로퍼티)가 좌측으로 병합되는 것에 주의할 것

var obj = {foo: 'bar', x: 42};

var clone1 = {...obj};
clone1; // Object { foo: "bar", x: 42 }
clone1 === obj; // false

var clone2 = {...obj, x: '사십이', c: 1};
clone2; // Object { foo: "bar", x: "사십이", c: 1 }
var arr = ['a', 'b', ['c', 'd']];

var clone3 = [...arr];
clone3; // Array(3) [ "a", "b", (2) […] ]
clone3 === arr; // false

var clone4 = [...arr[2]];
clone4; // Array [ "c", "d" ]

Object.assign()

var obj = {a: 1, b: 2};
var clone = Object.assign({}, obj);
clone; // Object { a: 1, b: 2 }

var clone2 = Object.assign({}, obj, {b: 3}); // 객체의 사본을 만들면서 일부 프로퍼티는 재할당
clone2; // Object { a: 1, b: 3 }

Object.entries() + Array.prototype.reduce()

/**
 * 객체 얕은 복제
 *
 * @param targetObj 대상 객체
 * @param ignoreNull 이 값이 true일 때, 프로퍼티의 값이 null이면 복제본에 포함하지 않음
 */
function shallowClone({targetObj, ignoreNull = false}) {
  return Object.entries(targetObj).reduce((prev, cur) => {
    if (ignoreNull) {
      if (cur[1] === null) {
        return prev;
      }
    }
    prev[cur[0]] = cur[1];
    return prev;
  }, {});
}

JSON 문자열로 바꾼뒤 다시 객체로 변환

JSON.stringify()JSON.parse()를 이용한 얕은 복제 방법. JSON 표현이 불가능한 타입(e.g., Function)은 복제할 수 없다. 일부 타입들을 제외하면 어쨋든 객체간 연결은 끊어지므로 깊은 복제로 분류하는 경우도 있다.

// 복제할 대상
var copyme = {
  child: {
    grandson: {
      txt: 'peek-a-boo!',
      fn: function () {
        console.log('don\'t leave me!')
      }
    }
  },
  txt: 'yo'
};

// 복제본
var newone = JSON.parse(JSON.stringify(copyme));

console.log(newone.child.grandson.txt); // peek-a-boo!
console.log(typeof newone.child.grandson.fn); // undefined, 함수는 복제 불가

깊은 복제 Deep Cloning

간단한 방법은 없고 따로 정의해야 함.

재귀 함수 방식

⚠️ 이 함수는 getter/setter 메서드가 필드로 바뀌는 하자가 있다:

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  let clone = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  return clone;
}

테스트 코드:

var child = {
  _x: 3,
  set x(value) {
    this._x = value;
  },
  get x() {
    return 65536;
  }
}

var clone = deepClone(child);
console.log(clone); // Object { _x: 123, x: 65536 }
console.log(child); // Object { _x: 123, x: Getter & Setter }

Object.keys(child); // Array [ "_x", "x" ]
child['x']; // 65536