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

5180

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

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

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

jschrab
источник
566
Эвал не зло. Использование Eval плохо это. Если вы боитесь его побочных эффектов, вы используете его неправильно. Побочные эффекты, которых вы боитесь, являются причинами его использования. Кто-нибудь, кстати, действительно ответил на ваш вопрос?
Джеймс
15
Клонирование объектов - сложная задача, особенно с пользовательскими объектами произвольных коллекций. Который, вероятно, почему нет готового способа сделать это.
b01
12
eval()как правило, плохая идея, потому что многие оптимизаторы движка Javascript вынуждены отключаться при работе с переменными, которые устанавливаются черезeval . Просто наличие eval()в вашем коде может привести к снижению производительности.
user56reinstatemonica8
2
Возможный дубликат самого элегантного способа клонирования объекта JavaScript
Джон Слегерс
12
Обратите внимание, что JSONметод потеряет все типы Javascript, которые не имеют эквивалента в JSON. Например: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))сгенерирует{a: null, b: null, c: null, g: false}
oriadam

Ответы:

4731

Родное глубокое клонирование

Он называется «структурированное клонирование», работает экспериментально в Node 11 и более поздних версиях и, надеюсь, появится в браузерах. Смотрите этот ответ для более подробной информации.

Быстрое клонирование с потерей данных - JSON.parse / stringify

Если вы не используете Date с, функцией, undefined, Infinity, Regexps, карты, наборы, Blobs, списки файлов, ImageDatas разреженных массивов, типизированные массивы или другими сложными типами в пределах вашего объекта, очень простой один вкладыш к глубоким клонировать объект:

JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Видеть ответ Corban для ориентиров.

Надежное клонирование с использованием библиотеки

Поскольку клонирование объектов не является тривиальным (сложные типы, циклические ссылки, функции и т. Д.), Большинство основных библиотек предоставляют функции для клонирования объектов. Не изобретайте колесо - если вы уже используете библиотеку, проверьте, есть ли у нее функция клонирования объектов. Например,

  • Лодаш - cloneDeep ; можно импортировать отдельно через lodash.clonedeep модуль и, вероятно, является вашим лучшим выбором, если вы еще не используете библиотеку, которая обеспечивает функцию глубокого клонирования
  • AngularJS - angular.copy
  • JQuery - jQuery.extend(true, { }, oldObject) ; .clone()только клоны DOM-элементов

ES6

Для полноты обратите внимание, что ES6 предлагает два механизма поверхностного копирования: Object.assign()и синтаксис распространения . который копирует значения всех перечисляемых собственных свойств из одного объекта в другой. Например:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
Dan Dascalescu
источник
7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js в строке 276 (есть немного кода, который делает что-то еще, но код "как это сделать в JS" есть :)
Rune FS
7
Вот код JS, стоящий за глубокой копией jQuery, для всех, кто интересуется: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W
194
Ого! Просто чтобы быть предельно ясным: не знаю, почему этот ответ был выбран в качестве правильного ответа, это был ответ на ответы, приведенные ниже: stackoverflow.com/a/122190/6524 (который рекомендовал .clone(), который не является правильным кодом для используя в этом контексте). К сожалению, этот вопрос подвергся стольким пересмотрам, что первоначальная дискуссия уже не очевидна! Пожалуйста, просто следуйте советам Корбана и напишите цикл или скопируйте свойства непосредственно в новый объект, если вам нужна скорость. Или проверьте это сами!
Джон Резиг
9
Это вопрос JavaScript (без упоминания о jQuery).
gphilip
60
Как можно было бы сделать это без использования JQuery?
Awesomeness01
2266

Проверьте этот тест: http://jsben.ch/#/bWfk9

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

JSON.parse(JSON.stringify(obj))

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

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

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

Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить НАМНОГО БОЛЬШЕ ЭФФЕКТИВНОСТИ, просто вставив процедуру клонирования и вручную создав объект.

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

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

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

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

Обновление для ES6

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

Object.assign({}, obj);
Corban Brook
источник
4
@trysis Object.create не клонирует объект, использует объект-прототип ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser
105
Этот метод также удалит keysиз вашего object, у которого есть functionsих значения, потому что JSONон не поддерживает функции.
Карлен Кишмирян
39
Также имейте в виду, что использование JSON.parse(JSON.stringify(obj))объектов Date также преобразует дату обратно в UTC в строковом представлении в формате ISO8601 .
dnlgmzddr
31
Подход JSON также подавляет циклические ссылки.
Богатый Ремер
28
@velop, Object.assign ({}, objToClone), похоже, делает мелкий клон - используя его во время игры в консоли инструментов разработчика, клон объекта все еще указывает на ссылку на клонированный объект. Так что я не думаю, что это действительно применимо здесь.
Гаррет Симпсон
473

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

var newObject = JSON.parse(JSON.stringify(oldObject));
Султан Шакир
источник
86
Недостаток этого подхода, как я только что обнаружил, состоит в том, что если у вашего объекта есть какие-либо функции (у моего есть внутренние методы получения и установки), то они теряются при
строковом преобразовании
31
@Jason, причина, по которой этот метод медленнее, чем поверхностное копирование (на глубоком объекте), заключается в том, что этот метод по определению является глубоким копированием. Но поскольку JSONон реализован в собственном коде (в большинстве браузеров), это будет значительно быстрее, чем при использовании любого другого решения глубокого копирования на основе javascript, и иногда может быть быстрее, чем метод поверхностного копирования на основе javascript (см. Jsperf.com/cloning). -ан-объект / 79 ).
MiJyn
35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer
32
эта техника уничтожит также все Dateобъекты, которые хранятся внутри объекта, преобразовав их в строковую форму.
fstab
13
Он не сможет скопировать что-либо, что не является частью спецификации JSON ( json.org )
cdmckay
397

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

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

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

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

const v8 = require('v8');

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

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

Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная 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;
};

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

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

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

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

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

Джереми Бэнкс
источник
3
@rynah Я только что посмотрел через спецификацию снова , и вы правы: history.pushState()и history.replaceState()методы как синхронно установить history.stateв структурированном клон первого аргумента. Немного странно, но это работает. Я обновляю свой ответ сейчас.
Джереми Бэнкс
40
Это так неправильно! Этот API не предназначен для использования таким образом.
Фардин К.
209
Как парень, который реализовал pushState в Firefox, я чувствую странную смесь гордости и отвращения к этому хаку. Молодцы ребята.
Джастин Л.
Взлом pushState или Notification не работает для некоторых типов объектов, таких как Function
Shishir Arora
323

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

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;
}
ConroyP
источник
20
Решение JQuery будет работать для элементов DOM, а не только для любого объекта. Mootools имеет тот же предел. Хотелось бы, чтобы у них был общий «клон» для любого объекта ... Рекурсивное решение должно работать для всего. Это, вероятно, путь.
jschrab
5
Эта функция прерывается, если клонируемый объект имеет конструктор, который требует параметров. Кажется, что мы можем изменить его на "var temp = new Object ()" и заставить его работать в каждом случае, нет?
Эндрю Арнотт
3
Эндрю, если вы измените его на var temp = new Object (), у вашего клона не будет такого же прототипа, как у исходного объекта. Попробуйте использовать: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
Лимкодер
1
Аналогично ответу limscoder, смотрите мой ответ ниже о том, как сделать это без вызова конструктора: stackoverflow.com/a/13333781/560114
Мэтт Браун
3
Для объектов, которые содержат ссылки на части (например, сети объектов), это не работает: если две ссылки указывают на один и тот же подобъект, копия содержит две разные его копии. И если есть рекурсивные ссылки, функция никогда не завершится (ну, по крайней мере, не так, как вы этого хотите :-) Для этих общих случаев вы должны добавить словарь уже скопированных объектов и проверить, скопировали ли вы его уже ... Программирование сложно, если вы используете простой язык
virtualnobi
153

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

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

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;
    }
  });
}
Евгений Тюрин
источник
82
Это не копирует рекурсивно, поэтому не предлагает решения проблемы клонирования объекта.
mwhite
5
Этот метод сработал, хотя я тестировал несколько и _.extend ({}, (obj)) был на порядок быстрее, чем FAR: в 20 раз быстрее, чем JSON.parse и на 60% быстрее, чем Object.assign, например. Он хорошо копирует все подобъекты.
Нико
11
@ Белый есть разница между клоном и глубоким клоном. Этот ответ действительно клонирует, но не глубоко клонирует.
Мейрион Хьюз
57
Оператор попросил глубокого клона. это не делает глубокий клон.
user566245
9
Этот метод делает ДОЛЖНУЮ копию , а не ГЛУБИНУЮ копию ! Из-за этого это совершенно неправильный ответ !
Бхарата
97

Код:

// 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
neaumusic
5
Я не понимаю эту функцию. Предположим from.constructor, Dateк примеру. Как будет ifдостигнут третий тест, если второй ifтест будет успешным и вызовет возврат функции (с тех пор Date != Object && Date != Array)?
Адам Макки
1
@AdamMcKee Потому что передача аргументов javascript и назначение переменных довольно сложны . Этот подход прекрасно работает, в том числе даты (которые действительно обрабатываются вторым тестом) - fiddle для тестирования здесь: jsfiddle.net/zqv9q9c6 .
Brichins
1
@NickSweeting: Попробуйте - может быть, это работает. Если нет - исправьте и обновите ответ. Вот как это работает здесь, в сообществе :)
Камарей
1
Эта функция не клонирует регулярное выражение в тесте, условие «from.constructor! = Object && from.constructor! = Array» всегда возвращает true для других конструкторов, таких как Number, Date и т. Д.
aMarCruz
95

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

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":{}}
Нияз
13
Это связано с еще одной глупостью в 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)
Vitim.us
Это назначит наследуемые перечисляемые свойства объекта obj непосредственно клону и предполагает, что объект obj является простым объектом.
RobG
Это также портит массивы, которые преобразуются в объекты с помощью цифровых клавиш.
клинок
Не проблема, если вы не используете нуль.
Хорхе Букаран
78

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

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

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

Когда массив содержит числа и строки - такие функции, как .slice (), .concat (), .splice (), оператор присваивания "=" и функция клона 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 имеет лучшую производительность:

tfmontague
источник
Я протестировал несколько из них, и _.extend ({}, (obj)) был на BY FAR самым быстрым: в 20 раз быстрее, чем JSON.parse и на 60% быстрее, чем Object.assign, например. Он хорошо копирует все подобъекты.
Нико
4
Все ваши примеры мелкие, один уровень. Это не хороший ответ. Вопрос касался глубокого клонирования, т.е. как минимум двух уровней.
Карл Моррисон
1
Глубокая копия - это когда объект копируется полностью без использования ссылочных указателей на другие объекты. Методы в разделе «Глубокое копирование массива объектов», такие как jQuery.extend () и пользовательская функция (которая является рекурсивной), копируют объекты с «минимум двумя уровнями». Итак, нет, не все примеры являются «одноуровневыми» копиями.
tfmontague
1
Мне нравится ваша пользовательская функция копирования, но вы должны исключить нулевые значения, в противном случае все нулевые значения преобразуются в объекты, то есть:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi
2
@HossamMourad - ошибка была исправлена ​​Josi 1 февраля (в комментарии выше), и мне не удалось правильно обновить ответ. Извините, что эта ошибка привела к рефакторингу вашей кодовой базы.
tfmontague
64
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});
Zibri
источник
Хороший ответ, но это не подходит для циклических ссылок.
Люк
59

Глубокое копирование объектов в 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.)

JSBEN.CH Производительность Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Производительность Глубокое копирование объектов в JavaScript

Tính Ngô Quang
источник
5
Object.assign()не выполняет глубокую копию
Роймунсон
1
Вы должны добавить тесты для них; что было бы очень полезно
jcollum
Когда я использовал «созданный метод» для объекта, содержащего массив, я не смог использовать pop () или splice (), я не понимаю, почему? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();он бросает: TypeError: tmp.title.pop is not a function(конечно, pop () работает нормально, если я просто do let tmp = data; но тогда я не могу изменить tmp без влияния на данные)
hugogogo
Эй, твой последний пример неверен. На мой взгляд, вы должны использовать _clone, а не _cloneDeep для неправильного примера.
kenanyildiz
Этот созданный метод (2.) не будет работать для массивов, не так ли?
Тойво Севен
57

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

Вы также можете найти его на npm . Может использоваться как для браузера, так и для Node.js.

Вот пример того, как его использовать:

Установите его с

npm install clone

или упакуй это с Эндером .

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

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

оборота Пворб
источник
3
Клон npm был неоценим для меня за клонирование произвольно вложенных объектов. Это правильный ответ.
Энди Рэй
какова производительность вашей библиотеки по сравнению с скажем JSON.parse(JSON.stringify(obj))?
pkyeck
Вот библиотека, которая утверждает, что есть более быстрые варианты. Не проверял все же.
pvorb
Хорошее решение, и это поддерживает циклические ссылки (в отличие от анализа JSON)
Лука
55

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) Подчеркиваем и загружаем:

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

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

Alireza
источник
2
клон в подчеркивании не является глубоким клоном в текущей версии
Rogelio
Спасибо. да, как новый документ для Underscore ... clone_.clone (объект) Создать мелко скопированный клон предоставленного простого объекта. Любые вложенные объекты или массивы будут скопированы по ссылке, а не скопированы. _.clone ({name: 'moe'}); => {name: 'moe'};
Алиреза,
59
Object.assignэто не глубокая копия. Пример: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Если бы это была глубокая копия, y.a.bвсе равно было бы c, но это сейчас d.
КБА
8
Object.assign () клонирует только первый уровень свойств!
Хемс
5
что такое функция cloneSO ()?
pastorello
53

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

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Джо
источник
16
Этот ответ на самом деле не имеет отношения к делу, поскольку вопрос таков: для данного экземпляра b как создать копию c, не зная о фабрике a или не желая использовать фабрику a. Причина, по которой человек может не захотеть использовать фабрику, заключается в том, что после создания экземпляра b могут быть инициализированы дополнительные данные (например, пользовательский ввод).
Ноэль Абрахамс
12
Это правда, что на самом деле это не ответ на вопрос, но я думаю, что важно быть здесь, потому что это ответ на вопрос, который, как я подозреваю, многие из приезжающих сюда действительно хотят задать.
точка с запятой
8
Извините, ребята, я не очень понимаю, почему так много голосов. Клонирование объекта является довольно четкой концепцией: вы создаете объект из ДРУГОГО объекта, и это не имеет ничего общего с созданием нового объекта с фабричным шаблоном.
открывается как
2
Хотя это работает для предопределенных объектов, «клонирование» таким способом не распознает новые свойства, добавленные к исходному объекту. Если вы создаете a, добавляете новое свойство в a, затем создаете b. б не будет иметь нового свойства. По сути, фабричный образец является неизменным для новых свойств. Это не парадигматическое клонирование. См: jsfiddle.net/jzumbrun/42xejnbx
Джон
1
Я думаю, что это хороший совет, так как вместо использования const defaultFoo = { a: { b: 123 } };вы можете пойти const defaultFoo = () => ({ a: { b: 123 } };и ваша проблема решена. Тем не менее, это действительно не ответ на вопрос. Это могло бы иметь больше смысла как комментарий к вопросу, а не как полный ответ.
Джош из Карибу
48

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

var newObject = _.clone(oldObject);
itadok
источник
24
У lodash есть метод cloneDeep, он также поддерживает другой параметр для клонирования, чтобы сделать его более глубоким: lodash.com/docs#clone и lodash.com/docs#cloneDeep
opensas
12
@opensas согласился. Lodash, как правило, лучше подчеркивания
нха,
7
Я защищаю удаление этого и всех других ответов, которые являются только однострочными ссылками на .clone(...)метод служебной библиотеки . Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку.
Джереми Бэнкс
41

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

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's 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 .

Редактировать:

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

/**
 * 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's 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();
    };
}
Мэтт Браун
источник
30

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Натан Роджерс
источник
Что-то не так с этим ответом? Это более полезно, поскольку является самостоятельным решением, но простым; но решение jQuery более популярно. Это почему?
Cerecem
Да, пожалуйста, дайте мне знать. Кажется, что работает как задумано, если где-то есть какой-то скрытый разрыв, мне нужно использовать другое решение.
Натан Роджерс
4
Для простого объекта это примерно в 6 раз медленнее в Chrome, чем данный ответ, и становится намного медленнее по мере роста сложности объекта. Он ужасно масштабируется и может очень быстро стать узким местом для вашего приложения.
тик
1
Вам не нужны данные, просто понимание того, что происходит. Этот метод клонирования сериализует весь объект в строку, а затем анализирует сериализацию этой строки для построения объекта. По сути, это будет намного медленнее, чем просто реорганизация памяти (что делают более сложные клоны). Но с учетом вышесказанного, для небольших и средних проектов (в зависимости от вашего определения «среднего размера») кого волнует, будет ли он даже в 1000 раз менее эффективным? Если ваши объекты маленькие, и вы не клонируете их, тонна 1000х практически ничего - все равно практически ничто.
machineghost
3
Кроме того, этот метод теряет методы (или любые вещи, которые не разрешены в JSON), плюс - JSON.stringify преобразует объекты Date в строки, ... и не наоборот;) Не используйте это решение.
Мистер МТ,
22

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

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

var newObject = object(oldObject);

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


РЕДАКТИРОВАТЬ:

Это 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". То, что я действительно искал, было функцией выше, поэтому я вернулся, чтобы поделиться. Я думаю, что спрашивающий искал это тоже.
Крис Броски
51
Разница между клоном / копированием и наследованием заключается в том, что - на вашем примере, когда я изменяю свойство oldObject, свойство также изменяется в newObject. Если вы делаете копию, вы можете делать то, что вы хотите с oldObject, не меняя newObject.
Чудесно
13
Это нарушит проверку hasOwnProperty, так что это довольно хакерский способ клонирования объекта и даст вам неожиданные результаты.
Корбан Брук
var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Коди
22

У Lodash есть хороший метод _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
открытий
источник
5
Я защищаю удаление этого и всех других ответов, которые являются только однострочными ссылками на .clone(...)метод служебной библиотеки . Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку.
Джереми Бэнкс
Более простой способ - использовать _.merge({}, objA). Если бы только lodash не мутировал объекты в первую очередь, тогда cloneфункция была бы не нужна.
Ребс
7
Поиск Google для клонирования объектов JS см. Здесь. Я использую Lodash, поэтому этот ответ важен для меня. Давайте не пойдем все "Википедия делеционист" на ответы, пожалуйста.
Ребс
2
В узле 9 JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) намного быстрее, чем _.deepClone (arrayOfAbout5KFlatObjects).
Дан Даскалеску
21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
Марк Сидаде
источник
17
Проблема с методом в том, что если у вас есть подчиненные объекты в объекте obj, будут клонированы их ссылки, а не значения каждого подобъекта.
Камарей
1
просто сделайте его рекурсивным, чтобы суб-объекты были глубоко клонированы.
Пятница
просто любопытно ... не будет ли переменная clone иметь указатели на свойства исходного объекта? потому что, кажется, нет нового выделения памяти
Рупеш Патель
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
Maël Nison
источник
6
Это может быть хорошо для простых объектов, но копирует только значения свойств. Он не затрагивает цепочку прототипов и при использовании Object.keysпропускает не перечисляемые и унаследованные свойства. Кроме того, он теряет дескрипторы свойств, выполняя прямое присваивание.
Мэтт Бирнер
Если вы тоже скопируете прототип, вам не хватит только не перечисляемых и дескрипторов свойств, да? Довольно хорошо. :)
Сэм
Помимо производительности, это действительно удобный способ поверхностного копирования объекта. Я часто использую это для сортировки поддельных свойств покоя в назначении деструктурирования в моих компонентах React.
mjohnsonengr
17

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

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

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

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

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)
Page Notes
источник
14
как другие отмечали в комментариях к ответу Ресига, если вы хотите клонировать массивоподобный объект, вы меняете {} на [] в вызове расширения, например, jQuery.extend (true, [], obj)
Anentropic
15

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

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

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

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

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

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

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

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

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

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

http://davidwalsh.name/javascript-clone

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

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

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

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

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

Michael Uzquiano
источник
13

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

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Тест циклического массива ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Функциональный тест ...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
neatonk
источник
11

AngularJS

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

var newObject = angular.copy(oldObject);
azerafati
источник
11

Я не согласен с ответом с наибольшим количеством голосов здесь . Рекурсивный Deep Clone это гораздо быстрее , чем JSON.parse (JSON.stringify (OBJ)) подход упоминается.

А вот функция для быстрого ознакомления:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
prograhammer
источник
2
Мне понравился этот подход, но он не обрабатывает даты должным образом; рассмотрите возможность добавления чего-то вроде if(o instanceof Date) return new Date(o.valueOf());после проверки на null `
Луис
Сбои в циклических ссылках.
Гарри
В последнем стабильном Firefox это намного дольше, чем другие стратегии на этой ссылке Jsben.ch, на порядок или более. Это бьет других в неправильном направлении.
WBT
11
// 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;
};
Дима
источник
10

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

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

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

Код:

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;
}
andrew
источник
9

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