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

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

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

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

Ответы [ 74 ]

3 голосов
/ 01 января 2016

Поскольку рекурсия слишком дорога для JavaScript, и большинство ответов, которые я нашел, используют рекурсию, в то время как подход JSON пропустит не-JSON-конвертируемые части (функция и т. Д.). Поэтому я провел небольшое исследование и нашел эту технику батута, чтобы избежать этого. Вот код:

/*
 * Trampoline to avoid recursion in JavaScript, see:
 *     http://www.integralist.co.uk/posts/js-recursion.html
 */
function trampoline() {
    var func = arguments[0];
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args[i - 1] = arguments[i];
    }

    var currentBatch = func.apply(this, args);
    var nextBatch = [];

    while (currentBatch && currentBatch.length > 0) {
        currentBatch.forEach(function(eachFunc) {
            var ret = eachFunc();
            if (ret && ret.length > 0) {
                nextBatch = nextBatch.concat(ret);
            }
        });

        currentBatch = nextBatch;
        nextBatch = [];
    }
};

/*
 *  Deep clone an object using the trampoline technique.
 *
 *  @param target {Object} Object to clone
 *  @return {Object} Cloned object.
 */
function clone(target) {
    if (typeof target !== 'object') {
        return target;
    }
    if (target == null || Object.keys(target).length == 0) {
        return target;
    }

    function _clone(b, a) {
        var nextBatch = [];
        for (var key in b) {
            if (typeof b[key] === 'object' && b[key] !== null) {
                if (b[key] instanceof Array) {
                    a[key] = [];
                }
                else {
                    a[key] = {};
                }
                nextBatch.push(_clone.bind(null, b[key], a[key]));
            }
            else {
                a[key] = b[key];
            }
        }
        return nextBatch;
    };

    var ret = target instanceof Array ? [] : {};
    (trampoline.bind(null, _clone))(target, ret);
    return ret;
};

Также посмотрите эту суть: https://gist.github.com/SeanOceanHu/7594cafbfab682f790eb

2 голосов
/ 26 февраля 2018

Просматривая этот длинный список ответов, были рассмотрены почти все решения, кроме одного, которое мне известно. Это список способов глубокого клонирования объекта VANILLA JS.

  1. JSON.parse (JSON.stringify (obj));

  2. Через history.state с помощью pushState или replaceState

  3. API веб-уведомлений, но у него есть обратная сторона: запрашивать у пользователя разрешения.

  4. Выполнение собственного рекурсивного цикла по объекту для копирования каждого уровня.

  5. Ответ, который я не увидел -> Использование ServiceWorkers. Сообщения (объекты), передаваемые взад и вперед между страницей и скриптом ServiceWorker, будут глубокими клонами любого объекта.

2 голосов
/ 19 июня 2015

Используйте Object.create(), чтобы получить prototype и поддержку instanceof, и используйте цикл for(), чтобы получить перечисляемые ключи:

function cloneObject(source) {
    var key,value;
    var clone = Object.create(source);

    for (key in source) {
        if (source.hasOwnProperty(key) === true) {
            value = source[key];

            if (value!==null && typeof value==="object") {
                clone[key] = cloneObject(value);
            } else {
                clone[key] = value;
            }
        }
    }

    return clone;
}
2 голосов
/ 05 апреля 2017

Не касаясь прототипного наследования, вы можете глубоко уединить объекты и массивы следующим образом:

function objectClone(o){
  var ot = Array.isArray(o);
  return o !== null && typeof o === "object" ? Object.keys(o)
                                                     .reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r)
                                                                                                                : (r[k] = o[k],r), ot ? [] : {})
                                             : o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
    arr = [1,2,[3,4,[5,6,[7]]]],
    nil = null,
  clobj = objectClone(obj),
  clarr = objectClone(arr),
  clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.log(arr, clarr);
2 голосов
/ 24 ноября 2016

class Handler {
  static deepCopy (obj) {
    if (Object.prototype.toString.call(obj) === '[object Array]') {
      const result = [];
      
      for (let i = 0, len = obj.length; i < len; i++) {
        result[i] = Handler.deepCopy(obj[i]);
      }
      return result;
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
      const result = {};
      for (let prop in obj) {
        result[prop] = Handler.deepCopy(obj[prop]);
      }
      return result;
    }
    return obj;
  }
}
2 голосов
/ 30 октября 2016

Это решение с рекурсией:

obj = {
  a: { b: { c: { d: ['1', '2'] } } },
  e: 'Saeid'
}
const Clone = function (obj) {
  
  const container = Array.isArray(obj) ? [] : {}
  const keys  = Object.keys(obj)
   
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if(typeof obj[key] == 'object') {
      container[key] = Clone(obj[key])
    }
    else
      container[key] = obj[key].slice()
  }
  
  return container
}
 console.log(Clone(obj))
2 голосов
/ 21 ноября 2016

Для дальнейшего использования можно использовать этот код

ES6:

_clone: function(obj){
    let newObj = {};
    for(let i in obj){
        if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
            newObj[i] = clone(obj[i]);
        } else{
            newObj[i] = obj[i];
        }
    }
    return Object.assign({},newObj);
}

ES5:

function clone(obj){
let newObj = {};
for(let i in obj){
    if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
        newObj[i] = clone(obj[i]);
    } else{
        newObj[i] = obj[i];
    }
}
return Object.assign({},newObj);

}

Eg

var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}

xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
2 голосов
/ 28 мая 2019

Object.assign({},sourceObj) клонирует объект, только если его свойство не имеет ключа ссылочного типа. бывший

obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);

clonedObj.b.push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip

Так что глубокого клонирования невозможно достичь таким способом.

Лучшее решение, которое работает, это

var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
2 голосов
/ 10 октября 2018

Если вы обнаружите, что делаете подобные вещи регулярно (например, создаете функцию отмены повторного выполнения), возможно, стоит посмотреть Immutable.js

const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );

console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"

https://codepen.io/anon/pen/OBpqNE?editors=1111

2 голосов
/ 23 августа 2018

Если ваш объект является вложенным и содержит объект данных, другой структурированный объект или какой-либо объект свойства и т. Д., То использование JSON.parse(JSON.stringify(object)) или Object.assign({}, obj) или $.extend(true, {}, obj) не будет работать. В этом случае используйте lodash. Это просто и легко ..

var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);

Теперь A будет вашим новым клоном obj без каких-либо ссылок.

...