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

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

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

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

Ответы [ 74 ]

20 голосов
/ 05 июля 2012

Однострочная копия с мелким копированием ( ECMAScript 5-е издание ):

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 6-е издание , 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
17 голосов
/ 14 мая 2016

Только потому, что я не увидел AngularJS , упомянутых и подумал, что люди могут захотеть узнать ...

angular.copy также предоставляет метод глубокого копирования объектов и массивов.

16 голосов
/ 17 октября 2010

Похоже, идеального оператора глубокого клонирования для массивоподобных объектов пока не существует. Как показано в приведенном ниже коде, клонер jQuery Джона Резига превращает массивы с нечисловыми свойствами в объекты, которые не являются массивами, а JSON-клонер RegDwight удаляет нечисловые свойства. Следующие тесты иллюстрируют эти пункты в нескольких браузерах:

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)
15 голосов
/ 07 мая 2013

У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект JavaScript» или нет.

Давайте также предположим, что вы намереваетесь создать полный клон без ссылок на прототип обратно на исходный объект. Если вас не интересует полный клон, вы можете использовать многие из подпрограмм Object.clone (), представленных в некоторых других ответах (шаблон Крокфорда).

Для простых старых объектов JavaScript проверенный и действительно хороший способ клонирования объекта в современных средах выполнения довольно прост:

var clone = JSON.parse(JSON.stringify(obj));

Обратите внимание, что исходный объект должен быть чистым объектом JSON. То есть все его вложенные свойства должны быть скалярами (например, логическое значение, строка, массив, объект и т. Д.). Любые функции или специальные объекты, такие как RegExp или Date, не будут клонированы.

Это эффективно? Черт возьми, да. Мы перепробовали все виды методов клонирования, и это работает лучше всего. Я уверен, что какой-нибудь ниндзя мог бы придумать более быстрый метод. Но я подозреваю, что мы говорим о предельной прибыли.

Этот подход просто прост и легко реализуем. Оберните его в удобную функцию, и если вам действительно нужно выжать немного, перейдите к более позднему времени.

Теперь для непростых объектов JavaScript не существует простого ответа. На самом деле, этого не может быть из-за динамической природы функций JavaScript и состояния внутреннего объекта. Глубокое клонирование структуры JSON с функциями внутри требует, чтобы вы воссоздали эти функции и их внутренний контекст. И у JavaScript просто нет стандартизированного способа сделать это.

Правильный способ сделать это, опять же, через удобный метод, который вы объявляете и повторно используете в своем коде. Удобный метод может быть наделен некоторым пониманием ваших собственных объектов, так что вы можете быть уверены, что правильно воссоздаете график в новом объекте.

Мы написаны по-своему, но лучший общий подход, который я видел, описан здесь:

http://davidwalsh.name/javascript-clone

Это правильная идея. Автор (Дэвид Уолш) прокомментировал клонирование обобщенных функций. Это то, что вы можете выбрать, в зависимости от вашего варианта использования.

Основная идея заключается в том, что вам нужно специально обрабатывать создание ваших функций (или, так сказать, прототипов) для каждого типа. Здесь он предоставил несколько примеров для RegExp и Date.

Этот код не только краткий, но и очень читаемый. Это довольно легко расширить.

Это эффективно? Черт возьми, да. Учитывая, что цель состоит в том, чтобы создать настоящий клон глубокой копии, вам придется пройтись по элементам графа исходного объекта. При таком подходе вы можете точно настроить, какие дочерние элементы обрабатывать, и как вручную обрабатывать пользовательские типы.

Итак, поехали. Два подхода. Оба, на мой взгляд, эффективны.

13 голосов
/ 03 апреля 2011

Обычно это не самое эффективное решение, но оно делает то, что мне нужно. Простые тестовые случаи ниже ...

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
12 голосов
/ 14 сентября 2016

AngularJS

Ну, если вы используете угловой, вы могли бы сделать это тоже

var newObject = angular.copy(oldObject);
11 голосов
/ 18 июня 2017

Я не согласен с ответом, набравшим наибольшее количество голосов здесь . Рекурсивный Deep Clone на намного быстрее , чем упомянутый подход 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
}
11 голосов
/ 28 апреля 2010
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};
9 голосов
/ 29 октября 2015

Для людей, которые хотят использовать версию JSON.parse(JSON.stringify(obj)), но без потери объектов Date, вы можете использовать второй аргумент parse метода , чтобы преобразовать строки обратно в Date:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}
8 голосов
/ 24 июля 2012

Вот всеобъемлющий метод clone (), который может клонировать любой объект JavaScript. Он обрабатывает почти все случаи:

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