Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript? - PullRequest
5052 голосов
/ 23 сентября 2008

Какой самый эффективный способ клонировать объект JavaScript? Я видел, как используется obj = eval(uneval(o));, но это нестандартно и поддерживается только Firefox .

Я делал такие вещи, как obj = JSON.parse(JSON.stringify(o));, но ставлю под сомнение эффективность.

Я также видел функции рекурсивного копирования с различными недостатками.
Я удивлен, что канонического решения не существует.

Ответы [ 74 ]

7 голосов
/ 22 октября 2015

Только когда вы можете использовать ECMAScript 6 или транспортеры .

Особенности:

  • Не будет вызывать геттер / сеттер при копировании.
  • Сохраняет метод получения / установки.
  • Сохраняет информацию о прототипе.
  • Работает как с объектно-литеральными , так и с функциональными OO стилями написания.

Код:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}
6 голосов
/ 05 августа 2016

Я использую библиотеку клонов npm. Видимо, это также работает в браузере.

https://www.npmjs.com/package/clone

let a = clone(b)
6 голосов
/ 11 октября 2017

Я опоздал, чтобы ответить на этот вопрос, но у меня есть другой способ клонирования объекта:

   function cloneObject(obj) {
        if (obj === null || typeof(obj) !== 'object')
            return obj;
        var temp = obj.constructor(); // changed
        for (var key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                obj['isActiveClone'] = null;
                temp[key] = cloneObject(obj[key]);
                delete obj['isActiveClone'];
            }
        }
        return temp;
    }



var b = cloneObject({"a":1,"b":2});   // calling

, что намного лучше и быстрее, чем:

var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));  

и

var a = {"a":1,"b":2};

// Deep copy
var newObject = jQuery.extend(true, {}, a);

У меня есть код, отмеченный бенчмарком, и вы можете проверить результаты здесь :

и делимся результатами: enter image description here Рекомендации: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

5 голосов
/ 17 июля 2016

Однострочное решение ECMAScript 6 (специальные типы объектов, такие как Date / Regex, не обрабатываются):

const clone = (o) =>
  typeof o === 'object' && o !== null ?      // only clone objects
  (Array.isArray(o) ?                        // if cloning an array
    o.map(e => clone(e)) :                   // clone each of its elements
    Object.keys(o).reduce(                   // otherwise reduce every key in the object
      (r, k) => (r[k] = clone(o[k]), r), {}  // and save its cloned value into a new object
    )
  ) :
  o;                                         // return non-objects as is

var x = {
  nested: {
    name: 'test'
  }
};

var y = clone(x);

console.log(x.nested !== y.nested);
5 голосов
/ 04 июня 2017

В Lodash есть функция, которая обрабатывает это для вас так.

var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};

var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}} 

Читайте документы здесь .

5 голосов
/ 23 марта 2016

Клонирование объекта с использованием сегодняшнего JavaScript: ECMAScript 2015 (ранее известный как ECMAScript 6)

var original = {a: 1};

// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);

// Method 2: New object with spread operator assignment.
var copy2 = {...original};

Старые браузеры могут не поддерживать ECMAScript 2015. Распространенным решением является использование компилятора JavaScript-JavaScript, такого как Babel, для вывода ECMAScript 5 версии вашего кода JavaScript.

Как указал @ jim-hall , это всего лишь мелкая копия . Свойства свойств копируются как ссылки: изменение одного из них приведет к изменению значения в другом объекте / экземпляре.

5 голосов
/ 26 марта 2018

ES 2017 пример:

let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
5 голосов
/ 24 апреля 2014

Я обычно использую var newObj = JSON.parse( JSON.stringify(oldObje) );, но вот более правильный способ:

var o = {};

var oo = Object.create(o);

(o === oo); // => false

Смотреть устаревшие браузеры!

4 голосов
/ 25 августа 2014

Для дальнейшего использования, текущий черновик ECMAScript 6 представляет Object.assign как способ клонирования объектов. Пример кода будет:

var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }

На момент написания поддержка ограничена Firefox 34 в браузерах , поэтому она пока не используется в производственном коде (если, конечно, вы не пишете расширение для Firefox).

4 голосов
/ 24 июня 2011

Это самый быстрый метод, который я создал, который не использует прототип, поэтому он будет поддерживать hasOwnProperty в новом объекте.

Решение состоит в том, чтобы перебрать свойства верхнего уровня исходного объекта, сделать две копии, удалить каждое свойство из оригинала, а затем сбросить исходный объект и вернуть новую копию. Он должен повторять столько раз, сколько свойств верхнего уровня. Это сохраняет все условия if, чтобы проверить, является ли каждое свойство функцией, объектом, строкой и т. Д., И не требует итерации каждого свойства-потомка.

Единственным недостатком является то, что исходный объект должен быть снабжен исходным созданным пространством имен, чтобы сбросить его.

copyDeleteAndReset:function(namespace,strObjName){
    var obj = namespace[strObjName],
    objNew = {},objOrig = {};
    for(i in obj){
        if(obj.hasOwnProperty(i)){
            objNew[i] = objOrig[i] = obj[i];
            delete obj[i];
        }
    }
    namespace[strObjName] = objOrig;
    return objNew;
}

var namespace = {};
namespace.objOrig = {
    '0':{
        innerObj:{a:0,b:1,c:2}
    }
}

var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';

console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
...