[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 표현이 불가능한 타입인 undefined, Symbol, Function, RegExp, Date, Map, Set 등은 복제할 수 없다. 어쨋든 객체간 연결은 끊어지므로 깊은 복제로 보는 사람도 있다.

// 복제할 대상
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

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

재귀 함수 방식

⚠️ 이 함수는 getters와 setters 메서드가 필드로 바뀌는 하자가 있다:

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

window.structuredClone()

최신 브라우저에서 지원하는 전역 메서드. JSON 방식보다 더 넓은 범위의 복제가 가능하지만 Function, DOM 노드 같은 일부 타입은 여전히 복제 불가.

// 상태 관리에서 불변성 유지
var currentState = {
  user: { id: 1, settings: { theme: 'dark' } },
  posts: [{ id: 1, content: 'Hello' }]
};

var nextState = structuredClone(currentState);
nextState.user.settings.theme = 'light';
// currentState는 변경되지 않음

// 복잡한 데이터 구조 복사
var complexData = {
  buffer: new ArrayBuffer(8),
  typed: new Uint8Array([1, 2, 3]),
  blob: new Blob(['hello']),
  set: new Set([1, 2, 3])
};

var copy = structuredClone(complexData);