[javascript] JavaScript에서 객체를 딥 복제하는 가장 효율적인 방법은 무엇입니까?

JavaScript 객체를 복제하는 가장 효율적인 방법은 무엇입니까? obj = eval(uneval(o));사용 된 것을 보았지만 비표준이며 Firefox에서만 지원됩니다 .

나는 같은 일을 obj = JSON.parse(JSON.stringify(o));했지만 효율성에 의문을 제기했다.

또한 다양한 결함이있는 재귀 복사 기능을 보았습니다.

정식 해결책이 없다는 것에 놀랐습니다.



답변

네이티브 딥 클로닝

이를 “구조적 복제”라고하며 노드 11 이상에서 실험적으로 작동하며 브라우저에 들어가기를 희망합니다. 자세한 내용은 이 답변 을 참조하십시오.

데이터 손실로 빠른 복제-JSON.parse / stringify

사용하지 않는 경우 Date의, 기능 undefined, Infinity개체 내 regexps ‘에,지도, 세트, 물방울,하는 파일 목록, ImageDatas, 스파 스 배열, 형식화 된 배열 또는 다른 복잡한 유형을, 깊은 복제에 대한 아주 간단한 하나 라이너는 것을 목적으로한다 :

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'
  re: /.*/,  // lost
}
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()

벤치 마크에 대해서는 Corban의 답변 을 참조하십시오 .

라이브러리를 사용한 안정적인 복제

객체 복제는 사소한 것이 아니기 때문에 (복잡한 유형, 순환 참조, 함수 등) 대부분의 주요 라이브러리는 객체를 복제하는 기능을 제공합니다. 바퀴를 재발 명하지 마십시오. 이미 라이브러리를 사용하고 있다면 객체 복제 기능이 있는지 확인하십시오. 예를 들어

ES6

: 완성도를 들어, 주 ES6는 두 개의 얕은 복사 메커니즘을 제공하는 Object.assign()확산 구문을 . 열거 가능한 모든 자체 속성 값을 한 개체에서 다른 개체로 복사합니다. 예를 들면 다음과 같습니다.

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax


답변

이 벤치 마크를 확인하십시오 : http://jsben.ch/#/bWfk9

속도가 주요 관심사였던 이전 테스트에서

JSON.parse(JSON.stringify(obj))

객체를 딥 복제하는 가장 느린 방법 입니다 ( 플래그가 10-20 %로 설정된 jQuery.extend 보다 느립니다 deep).

deep플래그가 false(얕은 복제)로 설정 되면 jQuery.extend는 매우 빠릅니다 . 형식 유효성 검사를위한 몇 가지 추가 논리가 포함되어 있고 정의되지 않은 속성 등을 복사하지 않기 때문에 좋은 옵션이지만 약간 느려질 수도 있습니다.

복제하려는 객체의 구조를 알고 있거나 깊은 중첩 배열을 피할 수있는 경우 for (var i in obj)hasOwnProperty를 확인하면서 객체를 복제 하는 간단한 루프를 작성할 수 있으며 jQuery보다 훨씬 빠릅니다.

마지막으로 핫 루프에서 알려진 객체 구조를 복제하려는 경우 단순히 복제 프로 시저를 인라인하고 수동으로 객체를 구성하여 훨씬 더 많은 성능을 얻을 수 있습니다.

JavaScript 추적 엔진은 for..in루프 를 최적화 하고 hasOwnProperty를 확인하면 속도가 느려집니다. 속도가 절대적인 경우 수동 복제.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

객체 에서 JSON.parse(JSON.stringify(obj))메소드 사용에주의 -ISO 형식으로 날짜의 문자열 표현을 반환하며 Date객체 로 다시 변환 되지 않습니다 . 자세한 내용은이 답변을 참조하십시오 .JSON.stringify(new Date())JSON.parse() Date

또한 Chrome 65에서는 최소한 기본 복제가 불가능합니다. JSPerf에 따르면, 새로운 기능을 만들어 네이티브 복제를 수행 하는 것은 JSON.stringify를 사용하는 것보다 거의 800 배 느리게 진행됩니다.

ES6 업데이트

Javascript ES6을 사용하는 경우 복제 또는 단순 복사를 위해이 기본 방법을 시도하십시오.

Object.assign({}, obj);


답변

객체에 함수가 아닌 변수 만 있다고 가정하면 다음을 사용할 수 있습니다.

var newObject = JSON.parse(JSON.stringify(oldObject));


답변

구조화 된 복제

HTML 표준에는 개체의 깊은 복제본을 만들 수 있는 내부 구조화 된 복제 / 직렬화 알고리즘 이 포함되어 있습니다. 여전히 특정 내장 유형으로 제한되지만 JSON에서 지원하는 몇 가지 유형 외에도 날짜, RegExps, 맵, 세트, ​​Blob, FileLists, ImageDatas, 스파 스 배열, 유형 배열 등을 지원합니다. . 또한 복제 된 데이터 내에서 참조를 유지하므로 JSON에 오류가 발생할 수있는 순환 및 재귀 구조를 지원할 수 있습니다.

Node.js 지원 : 실험적 ?

v8(노드 11 등) 현재 Node.js를의 모듈은 직접 구조 직렬화 API를 노출 하지만,이 기능은 여전히 “실험”, 미래의 버전에서 변경 또는 제거 대상으로 표시됩니다. 호환되는 버전을 사용하는 경우 객체 복제는 다음과 같이 간단합니다.

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

브라우저에서 직접 지원 : 어쩌면 결국? ?

브라우저는 현재 구조적 클로닝 알고리즘을위한 직접적인 인터페이스를 제공하지 않지만 GitHub의 whatwg / html # 793structuredClone() 에서 전역 함수에 대해 논의했습니다 . 현재 제안했듯이 대부분의 목적으로 사용하는 것은 다음과 같이 간단합니다.

const clone = structuredClone(original);

이것이 제공되지 않으면 브라우저의 구조화 된 클론 구현은 간접적으로 만 노출됩니다.

비동기 해결 방법 : 사용 가능 ?

기존 API로 구조화 된 복제본을 만드는 오버 헤드가 낮은 방법은 MessageChannels의 한 포트를 통해 데이터를 게시하는 것 입니다. 다른 포트는 message연결된의 복제 된 클론이 있는 이벤트를 생성합니다 .data. 불행히도 이러한 이벤트를 수신하는 것은 반드시 비동기식이며 동기식 대안은 실용적이지 않습니다.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

사용 예 :

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

동기 해결 방법 : 끔찍합니다! ?

구조적 클론을 동 기적으로 생성하기위한 좋은 옵션은 없습니다. 대신 몇 가지 비현실적인 해킹이 있습니다.

history.pushState()그리고 history.replaceState()모두 자신의 첫 번째 인수의 구조화 된 클론을 생성하고 해당 값을 할당합니다 history.state. 이것을 사용하여 다음과 같은 객체의 구조화 된 복제본을 만들 수 있습니다.

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

사용 예 :

동기식이지만 매우 느릴 수 있습니다. 브라우저 기록 조작과 관련된 모든 오버 헤드가 발생합니다. 이 메소드를 반복해서 호출하면 Chrome이 일시적으로 응답하지 않을 수 있습니다.

Notification생성자는 그와 연관된 데이터의 구조화 된 클론을 생성한다. 또한 사용자에게 브라우저 알림을 표시하려고 시도하지만 알림 권한을 요청하지 않으면 자동으로 실패합니다. 다른 목적으로 권한을 보유한 경우 Google이 생성 한 알림을 즉시 닫습니다.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

사용 예 :


답변

내장 된 것이 없다면 다음을 시도해보십시오.

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}


답변

한 줄의 코드로 객체를 복제 (심층 복제 아님)하는 효율적인 방법

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;
    }
  });
}


답변

암호:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

테스트:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);