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

4845

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

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

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

Я удивлен, что канонического решения не существует.

  • 553
    Эвал не злой. Использование eval плохо работает. Если вы боитесь его побочных эффектов, вы используете его неправильно. Побочные эффекты, которые вы боитесь, являются причинами его использования. Кто-нибудь, кстати, на самом деле ответил на ваш вопрос?
  • 15
    Клонирование объектов - сложная задача, особенно с пользовательскими объектами произвольных коллекций. Который, вероятно, почему нет готового способа сделать это.
Показать ещё 5 комментариев
Теги:
object
clone

68 ответов

4413
Лучший ответ

Примечание: Это ответ на другой ответ, а не правильный ответ на этот вопрос. Если вы хотите быстро клонировать объекты, пожалуйста, следуйте совету Corban в своем ответе на этот вопрос.


Хочу отметить, что метод .clone() в jQuery только клонирует элементы DOM. Чтобы клонировать объекты JavaScript, вы должны:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

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

Более подробную информацию можно найти в документации jQuery.

Я также хочу отметить, что глубокая копия на самом деле намного умнее, чем показано выше - она ​​способна избежать многих ловушек (например, для глубокого расширения элемента DOM). Он часто используется в ядре jQuery и в плагинах с большим эффектом.

  • 45
    Для тех, кто не осознавал, ответ Джона Резига, вероятно, был задуман как своего рода ответ / разъяснение ответа ConroyP , а не как прямой ответ на вопрос.
  • 7
    @ThiefMaster github.com/jquery/jquery/blob/master/src/core.js в строке 276 (есть немного кода, который делает что-то еще, но код "как это сделать в JS" есть :)
Показать ещё 32 комментария
1944

Ознакомьтесь с этим эталоном: http://jsben.ch/#/bWfk9

В моих предыдущих тестах, где скорость была главной проблемой, я обнаружил

<Предварительно > <код > JSON.parse(JSON.stringify(OBJ)) Код >

чтобы быть самым быстрым способом глубокого клонирования объекта (он превосходит jQuery.extend с установленным глубоким флагом на 10-20%).

jQuery.extend довольно быстро, когда для флага глубокого значения установлено значение false (мелкий клон). Это хороший вариант, поскольку он включает в себя дополнительную логику для проверки типов и не копирует поверх свойств undefined и т.д., Но это также немного замедлит вас.

Если вы знаете структуру объектов, которые вы пытаетесь клонировать или можете избежать глубоких вложенных массивов, вы можете написать простой цикл for (var я in obj), чтобы клонировать ваш объект, проверяя hasOwnProperty и он будет намного быстрее, чем jQuery.

Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить MUCH MORE MORE PERFORMANCE, просто вставив процедуру клонирования и вручную создав объект.

Механизмы отслеживания JavaScript сосать при оптимизации циклов for..in и проверка hasOwnProperty также замедлит вас. Ручной клон, когда скорость является абсолютной необходимостью.

  var clonedObject = { knownProp: obj.knownProp,..
}
Код>

Остерегайтесь использования метода JSON.parse(JSON.stringify(obj)) для объектов Date - JSON.stringify(новая дата()) возвращает строковое представление даты в формате ISO, в котором JSON.parse() не не возвращается обратно к объекту Date. См. этот ответ для более подробной информации.

Кроме того, обратите внимание, что в Chrome 65, по крайней мере, нативный клонирование не подходит. Согласно этот JSPerf, выполнение собственного клонирования путем создания новой функции почти 800x медленнее, чем использование JSON.stringify, которое невероятно быстро проходит через всю доску.

  • 4
    @trysis Object.create не клонирует объект, использует объект-прототип ... jsfiddle.net/rahpuser/yufzc1jt/2
  • 0
    Я нашел этот модуль npm, когда нашел этот вопрос. Отличный ответ. Интересно, есть ли отзывы по этому модулю? Я посмотрел на источник, и, кажется, все в порядке. Мне особенно нравится эта цитата: «копирует дескриптор ES5 каждого свойства, которое возвращает Object.getOwnPropertyDescriptor ()» .
Показать ещё 27 комментариев
452

Предполагая, что у вас есть только переменные, а не какие-либо функции в вашем объекте, вы можете просто использовать:

var newObject = JSON.parse(JSON.stringify(oldObject));
  • 0
    jQuery 1.5 теперь имеет новый метод jQuery.sub () ( api.jquery.com/jQuery.sub ) для всех, кто использует библиотеку.
  • 1
    есть ли причина, почему это не сработает? это кажется удивительно элегантным для меня. как его эффективность?
Показать ещё 10 комментариев
306

Структурированное клонирование

Стандарт HTML включает внутренний структурированный алгоритм клонирования/сериализации, который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Карты, Наборы, BLOB-объекты, FileLists, ImageDatas, разреженные массивы, Typed Arrays и, возможно, больше в будущем., Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.

Поддержка в Node.js: экспериментальная

Модуль v8 в Node.js в настоящее время (по состоянию на Node 11) напрямую предоставляет API структурированной сериализации, но эта функциональность по-прежнему помечена как "экспериментальная" и может быть изменена или удалена в будущих версиях. Если вы используете совместимую версию, клонирование объекта так же просто, как:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Прямая поддержка в браузерах: возможно, в конце концов?

Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structdClone structuredClone() обсуждалась в whatwg/html # 793 на GitHub. В настоящее время предлагается использовать его для большинства целей так же просто, как:

const clone = structuredClone(original);

Если это не предусмотрено, реализации структурированных клонов в браузерах предоставляются только косвенно.

Асинхронный обходной путь: можно использовать.

Более простой способ создания структурированного клона с существующими API-интерфейсами состоит в публикации данных через один порт MessageChannels. Другой порт отправит событие message со структурированным клоном прикрепленных данных .data. К сожалению, прослушивание этих событий обязательно асинхронно, а синхронные альтернативы менее практичны.

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. Вы можете использовать это для создания структурированного клона любого объекта, подобного этому:

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();
  • 3
    @rynah Я только что снова просмотрел спецификацию, и вы правы: методы history.pushState() и history.replaceState() синхронно устанавливают history.state для структурированного клона своего первого аргумента. Немного странно, но это работает. Я обновляю свой ответ сейчас.
  • 35
    Это так неправильно! Этот API не предназначен для использования таким образом.
Показать ещё 1 комментарий
294

Если бы не было встроенного, вы могли бы попробовать:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}
  • 20
    Решение JQuery будет работать для элементов DOM, а не только для любого объекта. Mootools имеет тот же предел. Хотелось бы, чтобы у них был общий «клон» для любого объекта ... Рекурсивное решение должно работать на все. Это, вероятно, путь.
  • 5
    Эта функция прерывается, если клонируемый объект имеет конструктор, который требует параметров. Кажется, что мы можем изменить его на "var temp = new Object ()" и заставить его работать в каждом случае, нет?
Показать ещё 8 комментариев
141

Эффективный способ клонирования (не глубокого клонирования) объекта в одной строке кода

Метод Object.assign является частью стандарта ECMAScript 2015 (ES6) и делает именно то, что вам нужно.

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

Метод 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;
    }
  });
}
  • 63
    Это не копирует рекурсивно, поэтому не предлагает решения проблемы клонирования объекта.
  • 0
    Этот метод работал для моих нужд, хотя в угловых приложениях дочерние элементы клонированных объектов рассматриваются как принадлежащие исходному объекту. : / Наверное, потому что это только клонирует свойства первого уровня? Точно сказать не могу.
Показать ещё 8 комментариев
90

код:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Тест:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);
  • 3
    как насчет var obj = {} и obj.a = obj
  • 5
    Я не понимаю эту функцию. Предположим, например, from.constructor это Date . Как бы третий , if тест будет достигнут , когда второй , if тест будет успешным и вызвать функцию для возврата (с Date != Object && Date != Array )?
Показать ещё 6 комментариев
82

Это то, что я использую:

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;
}
  • 8
    Это не кажется правильным. cloneObject({ name: null }) => {"name":{}}
  • 12
    Это связано с еще одной глупостью в javascript typeof null > "object" но Object.keys(null) > TypeError: Requested keys of a value that is not an object. измените условие на if(typeof(obj[i])=="object" && obj[i]!=null)
Показать ещё 3 комментария
68

Глубокая копия по производительности: от лучших до худших

  • Переназначение "=" (строковые массивы, числовые массивы - только)
  • Slice (строковые массивы, массивы чисел - только)
  • Конкатенация (только массивы строк, числовые массивы)
  • Пользовательская функция: for-loop или рекурсивная копия
  • jQuery $.extend
  • JSON.parse (только строковые массивы, массивы чисел, массивы объектов)
  • Underscore.js _.clone (только строковые массивы, числовые массивы)
  • Lo-Dash _.cloneDeep

Глубоко скопируйте массив строк или чисел (один уровень - без указателей):

Когда массив содержит числа и строки - такие функции, как.slice(),.concat(),.splice(), оператор присваивания "=" и функция clone Underscore.js; сделает глубокую копию элементов массива.

Если переназначение имеет самую высокую производительность:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

И.slice() имеет лучшую производительность, чем.concat(), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Глубоко скопируйте массив объектов (два или более уровней - указатели):

var arr1 = [{object:'a'}, {object:'b'}];

Напишите пользовательскую функцию (имеет более высокую производительность, чем $.extend() или JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Используйте сторонние служебные функции:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Где jQuery $.extend имеет лучшую производительность:

  • 0
    Я протестировал несколько из них, и _.extend ({}, (obj)) был на BY FAR самым быстрым: в 20 раз быстрее, чем JSON.parse и на 60% быстрее, чем Object.assign, например. Он хорошо копирует все подобъекты.
  • 0
    @NicoDurand - Ваши тесты производительности онлайн?
Показать ещё 5 комментариев
60
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});
53

Я знаю, что это старый пост, но я подумал, что это может помочь кому-то, кто спотыкается.

Пока вы не присваиваете объект чему-либо, он не поддерживает ссылку в памяти. Таким образом, чтобы создать объект, который вы хотите разделить между другими объектами, вам нужно создать factory так:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
  • 15
    Этот ответ на самом деле не актуален, потому что вопрос таков: для данного экземпляра b как создать копию c, НЕ Зная о фабрике a или не желая использовать фабрику a. Причина, по которой человек может не захотеть использовать фабрику, заключается в том, что после создания экземпляра b могут быть инициализированы дополнительные данные (например, пользовательский ввод).
  • 11
    Это правда, что на самом деле это не ответ на вопрос, но я думаю, что важно быть здесь, потому что это ответ на вопрос, который, как я подозреваю, многие из людей, приезжающих сюда, действительно хотят задать.
Показать ещё 3 комментария
50

Cloning Объект всегда был предметом беспокойства в JS, но все это было до ES6, я перечисляю различные способы копирования объекта в JavaScript ниже, представьте, что у вас есть Объект ниже и вы хотите иметь глубокую копию что:

var obj = {a:1, b:2, c:3, d:4};

Существует несколько способов копирования этого объекта без изменения источника:

1) ES5 +, используя простую функцию для копирования:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5 +, используя JSON.parse и JSON.stringify.

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

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs и Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Надеюсь, что эта помощь...

  • 2
    клон в подчеркивании не является глубоким клоном в текущей версии
  • 0
    Благодарю. да как новый документ для Underscore ... clone_.clone (объект) Создать мелко скопированный клон предоставленного простого объекта. Любые вложенные объекты или массивы будут скопированы по ссылке, а не скопированы. _.clone ({name: 'moe'}); => {name: 'moe'};
Показать ещё 7 комментариев
50

Если вы используете его, библиотека Underscore.js имеет clone.

var newObject = _.clone(oldObject);
  • 23
    У lodash есть метод cloneDeep, он также поддерживает другой параметр для клонирования, чтобы углубить его: lodash.com/docs#clone and lodash.com/docs#cloneDeep
  • 11
    @opensas согласился. Lodash, как правило, лучше подчеркивания
Показать ещё 1 комментарий
49

Theres a библиотека (называемая "клон" ), что делает это довольно хорошо. Он обеспечивает наиболее полное рекурсивное клонирование/копирование произвольных объектов, о которых я знаю. Он также поддерживает циркулярные ссылки, которые еще не охватываются другими ответами.

Вы можете найти его на 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' } }

(Отказ от ответственности: Im автор библиотеки.)

  • 2
    Клон npm был неоценим для меня за клонирование произвольно вложенных объектов. Это правильный ответ.
  • 0
    Какова производительность вашей библиотеки по сравнению, скажем, с JSON.parse(JSON.stringify(obj)) ?
Показать ещё 1 комментарий
36

Здесь приведенная выше версия ConroyP, которая работает, даже если у конструктора требуются параметры:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Эта функция также доступна в моей библиотеке simpleoo.

Edit:

Здесь более надежная версия (благодаря Джастину МакКэндлесу, теперь это поддерживает циклические ссылки):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
31

Следующее создает два экземпляра одного и того же объекта. Я нашел его и использую его в настоящее время. Это простой и простой в использовании.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
  • 0
    Что-то не так с этим ответом? Это более полезно, поскольку является самостоятельным решением, но простым; но решение jQuery более популярно. Это почему?
  • 0
    Да, пожалуйста, дайте мне знать. Кажется, что работает как задумано, если где-то есть какой-то скрытый разрыв, мне нужно использовать другое решение.
Показать ещё 5 комментариев
29

Глубокое копирование объектов в JavaScript (я думаю, лучший и самый простой)

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 link 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 link 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.)

Ссылка medium.com

JSBEN.CH Производительность Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Изображение 2006

  • 1
    Object.assign() не выполняет глубокое копирование
  • 1
    Вы должны добавить тесты для них; Это было бы очень полезно
Показать ещё 1 комментарий
24

Lodash имеет хороший метод _. cloneDeep (значение):

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
  • 2
    Я рекомендую удалить этот и все другие ответы, которые являются однострочными ссылками на метод .clone(...) служебной библиотеки. Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку.
  • 0
    Более простой способ - использовать _.merge({}, objA) . Если бы только lodash не мутировал объекты в первую очередь, тогда функция clone не была бы необходима.
Показать ещё 2 комментария
23

Крокфорд предлагает (и я предпочитаю) использовать эту функцию:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

Это краткое, работает так, как ожидалось, и вам не нужна библиотека.


EDIT:

Это polyfill для Object.create, поэтому вы также можете использовать это.

var newObject = Object.create(oldObject);

ПРИМЕЧАНИЕ.. Если вы используете некоторые из них, у вас могут быть проблемы с некоторой итерацией, которые используют hasOwnProperty. Потому что create создает новый пустой объект, который наследует oldObject. Но это все еще полезно и практично для клонирования объектов.

Например, если oldObject.a = 5;

newObject.a; // is 5

а

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
  • 9
    поправьте меня, если я ошибаюсь, но разве эта функция Крокфорда не вызывает наследование прототипа? Как это относится к клону?
  • 3
    Да, я боялся этого обсуждения: какова практическая разница между клонированием, копированием и наследованием прототипов, когда вы должны использовать каждый из них и какие функции на этой странице фактически делают что? Я нашел эту страницу SO, прибегая к помощи «объекта копирования javascript». То, что я действительно искал, было функцией выше, поэтому я вернулся, чтобы поделиться. Я предполагаю, что аскер тоже искал это.
Показать ещё 4 комментария
22
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
  • 15
    Проблема с методом в том, что если у вас есть подчиненные объекты в объекте obj, их ссылки будут клонированы, а не значения каждого подобъекта.
  • 1
    просто сделайте его рекурсивным, чтобы суб-объекты были глубоко клонированы.
Показать ещё 3 комментария
20

Однострочная копия мелкой копии (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 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
  • 6
    Это может быть хорошо для простых объектов, но копирует только значения свойств. Он не касается цепочки прототипов и, используя Object.keys пропускает не перечисляемые и унаследованные свойства. Кроме того, он теряет дескрипторы свойств, выполняя прямое присваивание.
  • 0
    Если вы тоже скопируете прототип, вам не хватит только не перечисляемых и дескрипторов свойств, да? Довольно хорошо. :)
Показать ещё 1 комментарий
17

Просто потому, что я не видел AngularJS и думал, что люди захотят узнать...

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

  • 0
    или он может быть использован так же, как jQiery extend: angular.extend({},obj);
  • 2
    @Galvani: Следует отметить, что jQuery.extend и angular.extend являются мелкими копиями. angular.copy - это глубокая копия.
16

Кажется, что нет идеального оператора глубокого клонирования для объектов типа массива. Как видно из приведенного ниже кода, клонирующий JQuery John Resig превращает массивы с нечисловыми свойствами в объекты, которые не являются массивами, а клонирование 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)
  • 14
    как другие отмечали в комментариях к ответу Ресига, если вы хотите клонировать массивоподобный объект, вы меняете {} на [] в вызове расширения, например, jQuery.extend (true, [], obj)
  • 0
    JSON.stringify не работает с functions
15

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

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

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

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

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

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

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

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

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

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

http://davidwalsh.name/javascript-clone

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

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

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

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

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

14

В Prototype вы сделаете что-то вроде

newObject = Object.clone(myObject);

Прототипная документация отмечает, что это делает мелкую копию.

13

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

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj 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
11

AngularJS

Хорошо, если вы используете angular, вы тоже можете это сделать

var newObject = angular.copy(oldObject);
10

Я не согласен с ответом с наибольшим количеством голосов здесь. Рекурсивный глубокий клон быстрее , чем упомянутый подход 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
}
  • 1
    Мне понравился этот подход, но он не обрабатывает даты должным образом; if(o instanceof Date) return new Date(o.valueOf()); добавить что-то вроде if(o instanceof Date) return new Date(o.valueOf()); после проверки на ноль
  • 0
    Сбои в циклических ссылках.
Показать ещё 1 комментарий
10
// 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;
};
8

Вот комплексный метод 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;
};
  • 0
    Он преобразует примитивы в объекты-обертки, что в большинстве случаев не является хорошим решением.
  • 0
    @DanubianSailor - я не думаю, что это так ... кажется, что он возвращает примитивы сразу с самого начала, и, похоже, не делает с ними ничего, что могло бы превратить их в объекты-оболочки по мере их возвращения.
6

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

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

let a = clone(b)
6

Клонирование объекта с использованием 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, это только мелкая копия. Свойства свойств копируются как ссылка: изменение одного изменит значение в другом объекте/экземпляре.

  • 24
    Это не касается глубоких слияний. gist.github.com/jimbol/5d5a3e3875c34abcf60a
  • 14
    Вау, этот ответ такой неправильный. Оба ваших метода делают мелкую копию одного уровня. Любой, кто смотрит на этот ответ, двигайтесь дальше.
6

Только если вы можете использовать ECMAScript 6 или transpilers.

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

  • При копировании не будет запускаться getter/setter.
  • Сохраняет геттер/сеттер.
  • Сохраняет информацию о прототипе.
  • Работает как с объектно-литеральным, так и функциональным 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

Я обычно использую var newObj = JSON.parse( JSON.stringify(oldObje) );, но здесь более подходящий способ:

var o = {};

var oo = Object.create(o);

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

Наблюдайте за старыми браузерами!

  • 0
    Второму способу нужен Prototype, я предпочитаю первый, даже если он не самый лучший по производительности, поскольку вы можете использовать его с большим количеством браузеров и с Node JS.
  • 0
    Это круто, и все, кроме предположения, что o имеет свойство a. Теперь oo.hasOwnProperty ('a')?
Показать ещё 2 комментария
5

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

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

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

и поделиться результатами: Изображение 2007 Ссылки: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

  • 0
    Забавно, но когда я запускаю ваши тесты, мне кажется, что метод 1 самый медленный
  • 0
    как и у меня, блок 1 самый низкий!
5

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

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

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

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

  • 0
    Я закончил тем, что использовал это, так как JSON.parse (JSON.stringify (obj)) не сохраняет первоначальный прототип объекта
  • 0
    Это мой готовый ответ. За исключением того, что я использую слияние Lodash, сохраняет синтаксис несколько согласованным для глубокого и мелкого копирования. //Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
5

Однострочное решение 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
    Пожалуйста, предоставьте объяснение вместе с фрагментом кода, чтобы другие с похожим вопросом могли легко понять, что происходит. В настоящее время этот вопрос находится в очереди на просмотр сообщений низкого качества.
  • 1
    Пожалуйста, отредактируйте с дополнительной информацией. Ответы «только код» и «попробуй это» не приветствуются, поскольку они не содержат контента для поиска и не объясняют, почему кто-то должен «попробовать это».
4

Пример ES 2017:

let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
  • 0
    Спасибо за ответ. Я попробовал ваш подход, но, к сожалению, он не работает. Поскольку это может быть какая-то ошибка с моей стороны, я прошу вас проверить мой пример в JSFiddle и, если это будет какой-то ошибкой с моей стороны, я проголосую за ваш ответ.
  • 0
    Когда я запускаю вашу скрипку, я получаю { foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3} и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4} . Разве это не то, что вы ожидаете?
Показать ещё 2 комментария
4

Для справки в будущем, текущий проект 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 расширение курса).

  • 3
    Вы, вероятно, имели в виду obj2 = Object.assign({}, obj1) . Ваш текущий код эквивалентен obj2 = obj1 .
  • 5
    Это мелкий клон. const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456; теперь o1.a.deep === 456 тоже.
Показать ещё 2 комментария
4

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

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

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

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

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

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}
3

Вот мой способ глубокого клонирования объекта с 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": "[email protected]"
    }
  },
  "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))
  • 0
    Это не глубокий клон, и вы делаете это неправильно.
  • 0
    объясните, пожалуйста, вместо того, чтобы говорить, что вы делаете это неправильно ...
3

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

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

Сообщите мне, что вы думаете.

3

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

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

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

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

/**
 * This is a quasi clone of jQuery 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;
}
  • 1
    Вы можете добавить || typeof target[name] !== "undefined" при тестировании if (target === to_add[name]) { continue; } чтобы не перезаписывать существующие члены target . Например, var a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a); мы ожидаем найти b => {hello:"you", foo:"bar"} , но с вашим кодом мы найдем: b => {hello:"world", foo:"bar"}
  • 0
    В моем случае я действительно ожидал перезаписать существующие элементы, поэтому текущее поведение было правильным для этого использования. Но спасибо вам добавить это полезное предложение.
3

Есть много ответов, но ни один из них не дал желаемого эффекта, в котором я нуждался. Я хотел использовать мощь глубокой копии 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

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

function clone(obj){
    if(typeof(obj) == 'function')//it a simple function
        return obj;
    //of it 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);            
}
2

В 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;
}
2

Для мелкой копии есть простой, простой метод, введенный в стандарте ECMAScript2018. Это связано с использованием оператора Spread:

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

let objClone = { ...obj };

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

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

  • 1
    обновление e в objClone все равно будет обновлять e в obj. Это все еще только мелкая копия. Вопрос явно требует глубокого клона.
  • 0
    @Taugenichts ... ты проверял это? Метод работает отлично. Spread_syntax Spread in object literals разделе Spread in object literals
Показать ещё 4 комментария
2

По моему опыту, рекурсивная версия значительно превосходит 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...

  • 0
    Можете ли вы объяснить, что происходит в этом коде?
  • 1
    Псевдокод будет: для каждого ключа присвойте его значение тому же ключу в новом объекте (мелкая копия). Однако, если значение имеет тип Object (нельзя поверхностное копирование), функция рекурсивно вызывает себя со значением в качестве аргумента.
2

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

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

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

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

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

}

Например,

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}}
  • 0
    это не относится к массивам, которые действительно являются объектами.
2

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

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

Поскольку рекурсия слишком дорогая для 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

  • 0
    Хвостовая рекурсия на самом деле очень эффективна в большинстве реализаций JavaScript и должна быть оптимизирована в ES6.
  • 0
    Привет, я тогда провел небольшой тест, и когда целевой объект становится сложным, стек вызовов легко переполняется, хотя я не делал никаких заметок, надеюсь, в es6 это будет большой операцией.
Показать ещё 2 комментария
2

Требуется новый-хэш-браузер, но...

Позвольте расширить собственный объект и получить реальный .extend();

Object.defineProperty(Object.prototype, 'extend', {
    enumerable: false,
    value: function(){
        var that = this;

        Array.prototype.slice.call(arguments).map(function(source){
            var props = Object.getOwnPropertyNames(source),
                i = 0, l = props.length,
                prop;

            for(; i < l; ++i){
                prop = props[i];

                if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
                    that[prop] = that[prop].extend(source[prop]);
                }else{
                    Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
                }
            }
        });

        return this;
    }
});

Просто поместите этот код перед любым кодом, который использует .extend() для объекта.

Пример:

var obj1 = {
    node1: '1',
    node2: '2',
    node3: 3
};

var obj2 = {
    node1: '4',
    node2: 5,
    node3: '6'
};

var obj3 = ({}).extend(obj1, obj2);

console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
  • 0
    Мутирование прототипов, как правило, считается плохой практикой, за исключением использования прокладок.
2

Используйте 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;
}
  • 0
    Отличный ответ! Я думаю, что это единственный метод, позволяющий сохранить сеттеры и геттеры без изменений. Это решило мою проблему. Спасибо! (см .: stackoverflow.com/questions/33207028/… )
  • 0
    Разве вы не хотите использовать clone = Object.create(Object.getPrototypeOf(source)) вместо наследования свойств, которые вы также перезаписываете?
Показать ещё 1 комментарий
1

если вы обнаружите, что делаете этот тип вещей обычным (eg-, создавая функциональные возможности отмены отмены), возможно, стоит посмотреть на 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

1

Когда ваш объект вложен и он содержит объект данных, другой структурированный объект или некоторый объект свойства и т.д., JSON.parse(JSON.stringify(object)) с помощью 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 без каких-либо ссылок..

1

Надеюсь это поможет.

function deepClone(obj) {
    /*
     * Duplicates an object 
     */

    var ret = null;
    if (obj !== Object(obj)) { // primitive types
        return obj;
    }
    if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
        ret = obj; // for ex: obj = new String("Spidergap")
    } else if (obj instanceof Date) { // date
        ret = new obj.constructor();
    } else
        ret = Object.create(obj.constructor.prototype);

    var prop = null;
    var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also


    var props = {};
    for (var i in allProps) {
        prop = allProps[i];
        props[prop] = false;
    }

    for (i in obj) {
        props[i] = i;
    }

    //now props contain both enums and non enums 
    var propDescriptor = null;
    var newPropVal = null; // value of the property in new object
    for (i in props) {
        prop = obj[i];
        propDescriptor = Object.getOwnPropertyDescriptor(obj, i);

        if (Array.isArray(prop)) { //not backward compatible
            prop = prop.slice(); // to copy the array
        } else
        if (prop instanceof Date == true) {
            prop = new prop.constructor();
        } else
        if (prop instanceof Object == true) {
            if (prop instanceof Function == true) { // function
                if (!Function.prototype.clone) {
                    Function.prototype.clone = function() {
                        var that = this;
                        var temp = function tmp() {
                            return that.apply(this, arguments);
                        };
                        for (var ky in this) {
                            temp[ky] = this[ky];
                        }
                        return temp;
                    }
                }
                prop = prop.clone();

            } else // normal object 
            {
                prop = deepClone(prop);
            }

        }

        newPropVal = {
            value: prop
        };
        if (propDescriptor) {
            /*
             * If property descriptors are there, they must be copied
             */
            newPropVal.enumerable = propDescriptor.enumerable;
            newPropVal.writable = propDescriptor.writable;

        }
        if (!ret.hasOwnProperty(i)) // when String or other predefined objects
            Object.defineProperty(ret, i, newPropVal); // non enumerable

    }
    return ret;
}

https://github.com/jinujd/Javascript-Deep-Clone

1

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

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

  • Через history.state с pushState или replaceState

  • API веб-уведомлений, но у этого есть недостаток в том, чтобы просить пользователя разрешений.

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

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

  • 0
    Все они уже были преобразованы либо в ответах, либо в комментариях. Я бы проголосовал, если бы вы дали уникальные примеры кода для каждого из них.
1

Поскольку этот вопрос имеет много внимания и ответов со ссылкой на встроенные функции, такие как Object.assign или пользовательский код для глубокого клонирования, я хотел бы поделиться некоторыми библиотеками с глубоким клоном,

1. esclone

npm install --savedev esclone https://www.npmjs.com/package/esclone

Пример использования ES6:

import esclone from "esclone";

const rockysGrandFather = {
  name: "Rockys grand father",
  father: "Don't know :("
};
const rockysFather = {
  name: "Rockys Father",
  father: rockysGrandFather
};

const rocky = {
  name: "Rocky",
  father: rockysFather
};

const rockyClone = esclone(rocky);

Пример использования ES5:

var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)

2. глубокая копия

npm установить глубокую копию https://www.npmjs.com/package/deep-copy

Пример:

var dcopy = require('deep-copy')

// deep copy object 
var copy = dcopy({a: {b: [{c: 5}]}})

// deep copy array 
var copy = dcopy([1, 2, {a: {b: 5}}])

3. Клон-глубокий

$npm install --save clone-deep https://www.npmjs.com/package/clone-deep

Пример:

var cloneDeep = require('clone-deep');

var obj = {a: 'b'};
var arr = [obj];

var copy = cloneDeep(arr);
obj.c = 'd';

console.log(copy);
//=> [{a: 'b'}] 

console.log(arr);
0
function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

используйте следующий метод вместо JSON.parse(JSON.stringify(obj)) потому что он медленнее, чем следующий метод

Как правильно клонировать объект JavaScript?

0

Как насчет слияния ключей объекта с его значениями?

function deepClone(o) {
    var keys = Object.keys(o);
    var values = Object.values(o);

    var clone = {};

    keys.forEach(function(key, i) {
        clone[key] = values[i];
    });

    return clone;
}


Или, если вы хотите использовать оператор распространения, даже если у него довольно ограниченная поддержка браузера, вот быстрый однострочный:
var clone = { ...originalObject };
0

Вы можете сделать это, как показано ниже:

var oldObj = [1,2,3,4,5];
var newObj = oldObj.map(x => Object.assign({}, x));
0
function deepCloneObj(obj){
  let newObj = {},
      mapLike = Object.entries(obj),
      deepCloneMapLikeObj = JSON.parse(JSON.stringify(map));

  for(let [key,value] of deepCloneMapLikeObj){
     newObj[key] = value;
  }
  return newObj;
}

Now Try yourself

var obj = {a:1,b:{c:2,d:3}};
var deepClone = deepCloneObj(obj);
deepClone.b.c = 9;
deepClone.b.c === obj.b.c // 9 === 3
0

Если вы используете ES6 (или выше) или typescript, вы можете попробовать

let original_obj = { 1:2, 3:4 };
let cloned_obj;
cloned_obj = { ...original_obj };
-1

Лучший и самый современный способ сделать этот клон выглядит следующим образом:

с помощью оператора распространения ES... Пример:

var clonedObjArray = [...oldObjArray];

таким образом мы распределяем массив на отдельные значения и помещаем его в новый массив с помощью оператора [].

вот длинный пример, показывающий разные способы его работы.

let objArray = [ {a:1} , {b:2} ];

let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array

console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );

objArray[0] = {c:3};

console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]
  • 3
    Это мелкий клон массива, а не глубокий клон объекта, и на первой странице есть существующий ответ, демонстрирующий оператор распространения для мелких объектов клонирования.
-3

В моих целях самым изящным способом создания нового объекта из существующего (клона) является использование функции "назначить" объекту JavaScript.

foo = {bar: 10, baz: {quox: 'batman'}};

clonedObject = Object.assign(foo);

clonedObject теперь является копией foo. Я не знаю деталей с точки зрения того, насколько хорошо это работает или что такое "глубокая" копия, но я использую ее, чтобы также объединить атрибуты объекта с другими объектами. Кажется, нужно работать и на клонирование.

  • 6
    Этот ответ нуждается в большем количестве голосов, это единственный ответ, который не зависит от сторонней библиотеки или EMCA6, которая еще не актуальна,
  • 1
    @ ninja08 если я изменю clonedObject.baz.quox = 'robin', то foo.baz.quox также будет 'robin'. Без глубокой копии ссылка на оригинальную базу остается неизменной.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню