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

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

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

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

Ответы [ 74 ]

4 голосов
/ 09 сентября 2017

Есть много способов достичь этого, но если вы хотите сделать это без какой-либо библиотеки, вы можете использовать следующее:

const cloneObject = (oldObject) => {
  let newObject = oldObject;
  if (oldObject && typeof oldObject === 'object') {
    if(Array.isArray(oldObject)) {
      newObject = [];
    } else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
      newObject = new Date(oldObject.getTime());
    } else {
      newObject = {};
      for (let i in oldObject) {
        newObject[i] = cloneObject(oldObject[i]);
      }
    }

  }
  return newObject;
}

Дайте мне знать, что вы думаете.

4 голосов
/ 15 октября 2017

Вот мой способ глубокого клонирования объекта с ES2015 значением по умолчанию и оператором распространения

 const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

const testObj = {
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "chance": "guid"
    },
    "emailAddr": {
      "type": "string",
      "chance": {
        "email": {
          "domain": "fake.com"
        }
      },
      "pattern": ".+@fake.com"
    }
  },
  "required": [
    "userId",
    "emailAddr"
  ]
}

const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

console.log(makeDeepCopy(testObj))
4 голосов
/ 20 января 2018

А как насчет асинхронного клонирования объектов, выполняемого Promise?

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}
4 голосов
/ 24 июня 2018

Для мелкой копии в стандарте ECMAScript2018 представлен отличный простой метод. Он включает использование оператора распространения :

let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };

let objClone = { ...obj };

Я проверил это в браузере Chrome, оба объекта хранятся в разных местах, поэтому изменение непосредственных дочерних значений в одном из них не изменит другого. Хотя (в примере) изменение значения в e повлияет на обе копии.

Эта техника очень проста и понятна. Я считаю, что это лучшая практика для этого вопроса раз и навсегда.

4 голосов
/ 05 ноября 2018

В JavaScript вы можете написать свой deepCopy метод как

function deepCopy(src) {
  let target = Array.isArray(src) ? [] : {};
  for (let prop in src) {
    let value = src[prop];
    if(value && typeof value === 'object') {
      target[prop] = deepCopy(value);
  } else {
      target[prop] = value;
  }
 }
    return target;
}
3 голосов
/ 20 июня 2018

По моему опыту, рекурсивная версия значительно превосходит JSON.parse(JSON.stringify(obj)). Вот модернизированная функция рекурсивного глубокого копирования объектов, которая может помещаться в одну строку:

function deepCopy(obj) {
  return Object.keys(obj).reduce((v, d) => Object.assign(v, {
    [d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
  }), {});
}

Это выполняется примерно в 40 раз быстрее , чем метод JSON.parse....

3 голосов
/ 29 июля 2013

Есть много ответов, но ни один из них не дал желаемого эффекта, который мне был нужен. Я хотел использовать всю мощь глубокой копии jQuery ... Однако, когда он сталкивается с массивом, он просто копирует ссылку на массив и глубоко копирует элементы в нем. Чтобы обойти это, я сделал небольшую рекурсивную функцию, которая автоматически создаст новый массив.

(Он даже проверяет kendo.data.ObservableArray, если вы этого хотите! Хотя убедитесь, что вы вызываете kendo.observable (newItem), если хотите, чтобы массивы снова стали наблюдаемыми.)

Итак, чтобы полностью скопировать существующий элемент, вы просто делаете это:

var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);


function createNewArrays(obj) {
    for (var prop in obj) {
        if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
            var copy = [];
            $.each(obj[prop], function (i, item) {
                var newChild = $.extend(true, {}, item);
                createNewArrays(newChild);
                copy.push(newChild);
            });
            obj[prop] = copy;
        }
    }
}
3 голосов
/ 28 марта 2011

Я думаю, что это лучшее решение, если вы хотите обобщить алгоритм клонирования объектов.
Его можно использовать как с jQuery, так и без него, хотя я рекомендую не использовать метод extend jQuery, если вы хотите, чтобы клонированный объект имел тот же «класс», что и исходный.

function clone(obj){
    if(typeof(obj) == 'function')//it's a simple function
        return obj;
    //of it's not an object (but could be an array...even if in javascript arrays are objects)
    if(typeof(obj) !=  'object' || obj.constructor.toString().indexOf('Array')!=-1)
        if(JSON != undefined)//if we have the JSON obj
            try{
                return JSON.parse(JSON.stringify(obj));
            }catch(err){
                return JSON.parse('"'+JSON.stringify(obj)+'"');
            }
        else
            try{
                return eval(uneval(obj));
            }catch(err){
                return eval('"'+uneval(obj)+'"');
            }
    // I used to rely on jQuery for this, but the "extend" function returns
    //an object similar to the one cloned,
    //but that was not an instance (instanceof) of the cloned class
    /*
    if(jQuery != undefined)//if we use the jQuery plugin
        return jQuery.extend(true,{},obj);
    else//we recursivley clone the object
    */
    return (function _clone(obj){
        if(obj == null || typeof(obj) != 'object')
            return obj;
        function temp () {};
        temp.prototype = obj;
        var F = new temp;
        for(var key in obj)
            F[key] = clone(obj[key]);
        return F;
    })(obj);            
}
3 голосов
/ 16 июня 2014

Это моя версия клонатора объектов. Это автономная версия метода jQuery, с небольшими изменениями и настройками. Проверьте скрипку . Я использовал много jQuery до того дня, когда понял, что большую часть времени буду использовать только эту функцию.

Использование такое же, как описано в API jQuery:

  • Неглубокий клон: extend(object_dest, object_source);
  • Глубокий клон: extend(true, object_dest, object_source);

Одна дополнительная функция используется для определения правильности клонирования объекта.

/**
 * This is a quasi clone of jQuery's extend() function.
 * by Romain WEEGER for wJs library - www.wexample.com
 * @returns {*|{}}
 */
function extend() {
    // Make a copy of arguments to avoid JavaScript inspector hints.
    var to_add, name, copy_is_array, clone,

    // The target object who receive parameters
    // form other objects.
    target = arguments[0] || {},

    // Index of first argument to mix to target.
    i = 1,

    // Mix target with all function arguments.
    length = arguments.length,

    // Define if we merge object recursively.
    deep = false;

    // Handle a deep copy situation.
    if (typeof target === 'boolean') {
        deep = target;

        // Skip the boolean and the target.
        target = arguments[ i ] || {};

        // Use next object as first added.
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== 'object' && typeof target !== 'function') {
        target = {};
    }

    // Loop trough arguments.
    for (false; i < length; i += 1) {

        // Only deal with non-null/undefined values
        if ((to_add = arguments[ i ]) !== null) {

            // Extend the base object.
            for (name in to_add) {

                // We do not wrap for loop into hasOwnProperty,
                // to access to all values of object.
                // Prevent never-ending loop.
                if (target === to_add[name]) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays.
                if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
                    if (copy_is_array) {
                        copy_is_array = false;
                        clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
                    }
                    else {
                        clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
                    }

                    // Never move original objects, clone them.
                    target[name] = extend(deep, clone, to_add[name]);
                }

                // Don't bring in undefined values.
                else if (to_add[name] !== undefined) {
                    target[name] = to_add[name];
                }
            }
        }
    }
    return target;
}

/**
 * Check to see if an object is a plain object
 * (created using "{}" or "new Object").
 * Forked from jQuery.
 * @param obj
 * @returns {boolean}
 */
function is_plain_object(obj) {
    // Not plain objects:
    // - Any object or value whose internal [[Class]] property is not "[object Object]"
    // - DOM nodes
    // - window
    if (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
        return false;
    }
    // Support: Firefox <20
    // The try/catch suppresses exceptions thrown when attempting to access
    // the "constructor" property of certain host objects, i.e. |window.location|
    // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
    try {
        if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
            return false;
        }
    }
    catch (e) {
        return false;
    }

    // If the function hasn't returned already, we're confident that
    // |obj| is a plain object, created by {} or constructed with new Object
    return true;
}
3 голосов
/ 18 апреля 2019

В 2019 году я использую:

deepCopy(object) {
  const getCircularReplacer = () => {
    const seen = new WeakSet();
    return (key, value) => {
      if(typeof value === 'object' && value !== null) {
        if(seen.has(value)) return;
        seen.add(value);
      }
      return value;
    };
  };
  return JSON.parse(JSON.stringify(object, getCircularReplacer()));
}

const theCopy = deepCopy(originalObject);

...