[javascript] JavaScript 객체를 올바르게 복제하려면 어떻게합니까?

나는 물건이있다 x. y변경 사항이 y수정되지 않도록 객체로 복사하고 싶습니다 x. 내장 JavaScript 객체에서 파생 된 객체를 복사하면 원치 않는 속성이 추가로 생성됩니다. 나는 문자 그대로 구성된 객체 중 하나를 복사하기 때문에 문제가되지 않습니다.

JavaScript 객체를 올바르게 복제하려면 어떻게합니까?



답변

JavaScript의 객체에 대해이 작업을 수행하는 것은 간단하거나 간단하지 않습니다. 프로토 타입에 남겨두고 새 인스턴스로 복사하지 않아야하는 객체의 프로토 타입에서 속성을 잘못 선택하는 문제가 발생합니다. 예를 들어에 대한 clone메소드를 추가하는 경우 Object.prototype일부 답변에서 볼 수 있듯이 해당 속성을 명시 적으로 건너 뛰어야합니다. 그러나 모르는 다른 추가 방법 Object.prototype이나 다른 중간 프로토 타입에 추가 된 방법 은 무엇입니까? 이 경우, 사용하지 말아야 할 속성을 복사하므로 hasOwnProperty메소드 를 사용하여 예상치 못한 로컬이 아닌 속성을 감지해야합니다 .

열거 할 수없는 속성 외에도 숨겨진 속성이있는 객체를 복사하려고하면 더 어려운 문제가 발생합니다. 예를 들어, prototype함수의 숨겨진 속성입니다. 또한 객체의 프로토 타입은 속성으로 참조되며이 속성 __proto__도 숨겨져 있으며 소스 객체의 속성을 반복하는 for / in 루프에 의해 복사되지 않습니다. __proto__Firefox의 JavaScript 인터프리터에만 해당되는 것으로 생각 되며 다른 브라우저에서는 다를 수 있지만 그림이 나타납니다. 모든 것이 열거 가능한 것은 아닙니다. 숨겨진 속성의 이름을 알고 있으면 복사 할 수 있지만 자동으로 검색하는 방법은 모르겠습니다.

우아한 솔루션을 찾는 또 다른 걸림돌은 프로토 타입 상속을 올바르게 설정하는 문제입니다. 소스 객체의 프로토 타입이 Object인 경우 간단히 새 일반 객체를 만드는 것이 {}작동하지만 소스의 프로토 타입이 일부 자손 인 Object경우 해당 프로토 타입에서 hasOwnProperty필터를 사용하여 건너 뛴 추가 멤버가 누락 되거나 프로토 타입에 있었지만 처음에는 열거 할 수 없었습니다. 한 가지 해결 방법은 소스 객체의 constructor속성 을 호출하여 초기 복사 객체를 가져온 다음 속성을 복사하는 것이지만 열거 할 수없는 속성은 얻지 못할 수 있습니다. 예를 들어 Date객체는 데이터를 숨겨진 멤버로 저장합니다.

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

의 날짜 문자열은의 날짜 문자열 d1보다 5 초 뒤에 있습니다 d2. 하나 Date를 다른 것과 동일 하게 만드는 setTime방법 은 메소드 를 호출하는 것이지만 Date클래스 에 따라 다릅니다 . 나는이 문제에 대한 방탄적인 일반적인 해결책이 없다고 생각하지만 잘못되어 기쁠 것입니다!

나는 내가 단지 일반 복사 할 필요가 있음을 가정하여 손상 결국 복사 일반적으로 깊은을 구현했다 때 Object, Array, Date, String, Number, 또는 Boolean. 마지막 3 가지 유형은 변경할 수 없으므로 얕은 복사를 수행하고 변경에 대해 걱정할 필요가 없습니다. 또한 그 목록에 포함 Object되거나 Array그 목록에있는 6 가지 간단한 유형 중 하나 일 것이라고 가정했습니다 . 이것은 다음과 같은 코드로 수행 할 수 있습니다.

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

위의 함수는 객체와 배열의 데이터가 트리 구조를 형성하는 한 위에서 언급 한 6 가지 간단한 유형에 적절하게 작동합니다. 즉, 객체에 동일한 데이터에 대한 참조가 두 개 이상 없습니다. 예를 들면 다음과 같습니다.

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

JavaScript 객체를 처리 할 수는 없지만 던질 때만 작동한다고 가정하지 않는 한 많은 목적으로 충분할 수 있습니다.


답변

Date객체 내에서 s, 함수, undefined, regExp 또는 Infinity를 사용하지 않는 경우 매우 간단한 라이너는 JSON.parse(JSON.stringify(object))다음과 같습니다.

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

이것은 객체, 배열, 문자열, 부울 및 숫자를 포함하는 모든 종류의 객체에 적용됩니다.

작업자와 메시지를 게시 할 때 사용되는 브라우저의 구조화 된 클론 알고리즘에 대한이 기사를 참조하십시오 . 또한 심층 복제 기능도 포함합니다.


답변

jQuery를 사용하면 extend로 얕은 복사 를 할 수 있습니다 .

var copiedObject = jQuery.extend({}, originalObject)

에 대한 후속 변경 사항 copiedObject은에 영향을 미치지 않으며 originalObject그 반대도 마찬가지입니다.

또는 깊은 사본 을 만들려면 :

var copiedObject = jQuery.extend(true, {}, originalObject)


답변

ECMAScript 6에는 열거 가능한 모든 자체 속성 값을 한 객체에서 다른 객체로 복사하는 Object.assign 메소드 가 있습니다. 예를 들면 다음과 같습니다.

var x = {myProp: "value"};
var y = Object.assign({}, x); 

그러나 중첩 된 개체는 여전히 참조로 복사됩니다.


답변

MDN 당 :

  • 얕은 복사를 원한다면 Object.assign({}, a)
  • “딥”복사의 경우 JSON.parse(JSON.stringify(a))

외부 라이브러리는 필요하지 않지만 먼저 브라우저 호환성 을 확인해야합니다 .


답변

많은 답변이 있지만 ECMAScript 5의 Object.create 에 대한 언급 은 없지만 정확한 사본을 제공하지는 않지만 소스를 새 객체의 프로토 타입으로 설정합니다.

따라서 이것은 질문에 대한 정확한 대답은 아니지만 단선 솔루션이므로 우아합니다. 그리고 두 가지 경우에 가장 효과적입니다.

  1. 그러한 상속이 유용한 곳 (duh!)
  2. 소스 객체가 수정되지 않는 경우 두 객체 간의 관계가 문제가되지 않습니다.

예:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

왜이 솔루션이 우수하다고 생각합니까? 그것은 네이티브이므로 반복도없고 재귀도 없습니다. 그러나 오래된 브라우저에는 폴리 필이 필요합니다.


답변

한 줄의 코드로 Javascript 객체를 복제하는 우아한 방법

Object.assign방법은 ECMAScript를 2015 년 (ES6) 표준의 일부이며 정확하게 당신이 필요로한다.

var clone = Object.assign({}, obj);

Object.assign () 메소드는 열거 가능한 모든 고유 특성의 값을 하나 이상의 소스 오브젝트에서 대상 오브젝트로 복사하는 데 사용됩니다.

더 읽어보기 …

구형 브라우저를 지원 하는 폴리 필 :

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}