object 자바 스크립트 - JavaScript에서 객체를 깊이 복제하는 가장 효율적인 방법은 무엇입니까?





15 Answers

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

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

JSON.parse(JSON.stringify(obj))

오브젝트를 깊게 복제하는 가장 빠른 방법입니다 ( jQuery.extend 와 deep 플래그가 10-20 %로 설정 됨).

deep 플래그가 false (얕은 복제)로 설정된 경우 jQuery.extend는 매우 빠릅니다. 이것은 타입 검증을위한 약간의 로직을 포함하고 정의되지 않은 프로퍼티 등을 복사하지 않기 때문에 좋은 옵션입니다.하지만 이것 역시 조금 느려질 것입니다.

복제하려는 객체의 구조를 알고 있거나 중첩 된 배열을 피할 수있는 경우 hasOwnProperty를 검사하는 동안 객체를 복제하기 for (var i in obj) 루프 for (var i in obj) 간단한 객체를 작성하면 jQuery보다 훨씬 빠릅니다.

마지막으로 핫 루프에서 알려진 오브젝트 구조를 복제하려고하면 복제 절차를 간단히 작성하고 수동으로 오브젝트를 구성하여 훨씬 더 많은 성능을 얻을 수 있습니다.

JavaScript 추적 엔진은 ~ for..in 루프를 최적화하고 hasOwnProperty를 검사하면 속도가 느려집니다. 속도가 절대적 일 때 수동 클론.

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

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

또한 Chrome 65 이상에서 기본 복제는 갈 길이 멀지 않습니다. 이 JSPerf 에 따르면 새 함수를 만들어 네이티브 복제를 수행하는 작업은 JSON.stringify를 사용하는 것보다 거의 800 배 정도 속도가 느립니다.

extend deep clone

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

obj = JSON.parse(JSON.stringify(o)); 와 같은 작업을 수행했습니다 obj = JSON.parse(JSON.stringify(o)); 그러나 효율성에 의문을 제기하십시오.

또한 여러 가지 결함이있는 재귀 복사 기능을 보았습니다.
나는 정식 해결책이 존재하지 않는다는 것에 놀랐다.




구조화 된 복제

HTML5는 개체의 딥 클론을 만들 수 있는 내부의 "구조화 된"복제 알고리즘 을 정의합니다. 여전히 특정 내장 유형에 국한되지만 JSON에서 지원하는 몇 가지 유형 외에도 Dates, RegExps, Maps, Sets, Blob, FileLists, ImageDatas, 희소 배열, 유형 지정 배열 등을 지원합니다. . 또한 복제 된 데이터 내에서 참조를 유지하므로 JSON에 오류를 일으킬 수있는 순환 및 순환 구조를 지원할 수 있습니다.

브라우저에서 직접 지원 : 곧 출시 될 예정입니까? 🙂

브라우저는 현재 구조화 된 복제 알고리즘에 대한 직접 인터페이스를 제공하지 않지만 GitHub의 whatwg / html # 793에서 global structuredClone() 함수가 활발하게 논의되고 있으며 곧 제공 될 예정입니다. 현재 제안 된대로 대부분의 용도로 사용하는 것은 다음과 같이 간단합니다.

const clone = structuredClone(original);

이것이 출시 될 때까지 브라우저의 구조화 클론 구현은 간접적으로 만 노출됩니다.

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

기존 API를 사용하여 구조화 된 복제본을 만드는 오버 헤드가 낮은 방법은 MessageChannels 한 포트를 통해 데이터를 게시하는 것입니다. 다른 포트는 첨부 된 .data 의 구조화 된 복제본과 함께 message 이벤트를 방출합니다. 불행히도 이러한 이벤트를 청취하는 것은 반드시 비동기 적이며 동기식 대안은 실용적이지 않습니다.

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 값을 history.state 할당합니다. 이것을 사용하여 다음과 같은 객체의 구조화 된 복제본을 만들 수 있습니다.

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

사용 예 :

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();

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

Notification 생성자 는 관련 데이터의 구조화 된 복제본을 만듭니다. 또한 사용자에게 브라우저 알림을 표시하려고 시도하지만 알림 권한을 요청하지 않은 경우 자동으로 실패합니다. 다른 용도로 사용 허가를받은 경우 즉시 작성한 알림을 닫습니다.

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

사용 예 :

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();




한 줄의 코드에서 객체를 복제하는 (효율적인 복제가 아닌) 효율적인 방법

Object.assign 메서드는 ECMAScript 2015 (ES6) 표준의 일부이며 필요한 작업을 정확하게 수행합니다.

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

Object.assign () 메서드는 열거 가능한 모든 속성의 값을 하나 이상의 소스 객체에서 대상 객체로 복사하는 데 사용됩니다.

Object.assign

이전 브라우저를 지원하는 polyfill :

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



이것이 제가 사용하고있는 것입니다 :

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}



var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});



라이브러리 ( "복제본"이라고 함) 가 있습니다. 그것은 내가 아는 임의의 객체의 가장 완전한 재귀 적 복제 / 복사를 제공합니다. 또한 순환 참조를 지원하며 다른 응답에서는 아직 다루지 않습니다.

npm 에서도 찾을 수 있습니다. Node.js뿐만 아니라 브라우저에서도 사용할 수 있습니다.

다음은이를 사용하는 예제입니다.

함께 설치

npm install clone

Ender 포장하십시오.

ender build clone [...]

소스 코드를 수동으로 다운로드 할 수도 있습니다.

그런 다음 소스 코드에서 사용할 수 있습니다.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(면책 조항 : 저는 도서관의 저자입니다.)




사용중인 경우 Underscore.js 라이브러리에는 clone 메소드가 있습니다.

var newObject = _.clone(oldObject);



다음은 동일한 객체의 두 인스턴스를 만듭니다. 나는 그것을 발견했고 그것을 현재 사용하고있다. 간단하고 사용하기 쉽습니다.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));



Lodash에는 좋은 lodash.com/docs#cloneDeep 메서드가 있습니다.

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false



얕은 사본 한 줄짜리 ( ECMAScript 5th edition ) :

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

그리고 얕은 복사 원 라이너 ( ECMAScript 6th edition , 2015) :

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true



배열과 같은 객체에 대해서는 아직 이상적인 딥 클론 연산자가없는 것 같습니다. 아래의 코드에서 알 수 있듯이 John Resig의 jQuery 복제자는 배열이 아닌 객체로 숫자가 아닌 속성의 배열을 변환하고 RegDwight의 JSON 복제자는 비 숫자 속성을 삭제합니다. 다음 테스트는 여러 브라우저에서 이러한 점을 보여줍니다.

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)



이것은 일반적으로 가장 효율적인 솔루션은 아니지만 필요한 것은 수행합니다. 아래의 간단한 테스트 사례 ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

순환 배열 테스트 ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

기능 검사...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false



나는 here 가장 큰 표를 던지는 대답에 동의하지 않는다 . 재귀 깊은 클론 입니다 훨씬 빠르게JSON.parse (JSON.stringify (OBJ)) 언급 한 방법.

다음은 빠른 참조 기능입니다.

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}



다음은 모든 JavaScript 객체를 복제 할 수있는 포괄적 인 clone () 메소드입니다. 거의 모든 경우를 처리합니다.

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};



자바 스크립트에서 오브젝트를 딥 복사하는 것 (가장 좋고 간단한 방법이라고 생각합니다)

1. JSON.parse (JSON.stringify (object)) 사용;

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2. 생성 된 메소드 사용

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Lo-Dash의 _.cloneDeep 링크 lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Object.assign () 메소드 사용하기

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

그러나 때때로

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Underscore.js 사용 .clone 링크 Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

그러나 때때로

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

매체 닷컴 참조

JSBEN.CH 성능 벤치마킹 놀이터 1 ~ 3 http://jsben.ch/KVQLd




Related