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

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

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

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


person Community    schedule 23.09.2008    source источник
comment
Эвал не зло. Плохое использование eval. Если вы боитесь его побочных эффектов, вы неправильно его используете. Побочные эффекты, которых вы боитесь, - это причины для его использования. Кстати, действительно ли кто-нибудь ответил на ваш вопрос?   -  person Heavy Gray    schedule 22.03.2012
comment
Клонирование объектов - непростая задача, особенно с пользовательскими объектами из произвольных коллекций. Вероятно, поэтому нет готового способа сделать это.   -  person b01    schedule 12.03.2013
comment
вот сравнение производительности наиболее распространенных типов объектов клонирования: jsben.ch/#/t917Z   -  person EscapeNetscape    schedule 17.10.2016
comment
Обратите внимание, что метод 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}   -  person oriadam    schedule 24.05.2017
comment
Сообщество React представило помощник по неизменяемости   -  person Navid    schedule 06.07.2019


Ответы (63)


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

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

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

Если вы не используете Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays или другие сложные типы в вашем объекте, очень простой лайнер для глубокого клонирования объекта:

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

См. Корбана ответьте для тестов.

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

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

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

ES6 (мелкая копия)

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

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
person Community    schedule 23.09.2008
comment
Остерегаться! var A = { b: [ { a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] } ] }; B = Object.assign( {}, A ); delete B.b[0].b; Он также изменит объект A! - person Gabriel Hautclocq; 30.09.2020
comment
@GabrielHautclocq какое решение в этом случае? - person medBouzid; 19.10.2020
comment
@GabrielHautclocq Вы только что узнали, что означает неглубокая копия. @medBouzid Решение состоит в том, чтобы глубоко скопировать объект вместо поверхностного копирования. - person Utku; 25.10.2020
comment
Я просто продемонстрировал на примере, что нельзя использовать Object.assign для глубокого клонирования объекта. - person Gabriel Hautclocq; 25.10.2020
comment
Object.assign() действительно бесполезен, если ваш объект для копирования содержит в себе массивы, особенно массивы объектов. Просто выяснил это на собственном горьком опыте ... - person cst1992; 29.10.2020
comment
@Gabriel Hautclocq, это потому, что A.b или B.b оба относятся к одному и тому же объекту в памяти. если A имеет свойство с не объектным значением (например, числа или строки), оно будет скопировано обычным образом. Но когда копируется свойство, содержащее значение объекта, оно копируется по ссылке, а не по значению. Также имейте в виду, что массив - это объект в JS. доказательство: typeof [] == 'object' && [] instanceof Array - person Unicornist; 15.12.2020
comment
@Unicornist Да, и именно поэтому Object.assign не отвечает на вопрос: каков наиболее эффективный способ глубокого клонирования объекта в JavaScript ?. Так что, по крайней мере, его НЕ следует представлять как решение ES6 для глубокого клонирования. Название ES6 вводит в заблуждение, по крайней мере, его следует изменить, чтобы отразить, что это не метод глубокого клонирования. Это поверхностное слово легко упустить из виду, и многие люди просто берут самое простое решение, которое они находят в Stack Overflow, не читая все. Опасно полагаться на Object.assign при клонировании объекта. Отсюда и мое замечание. - person Gabriel Hautclocq; 15.12.2020
comment
Я использовал библиотеку под названием действительно быстрое глубокое клонирование: github.com/davidmarkclements/rfdc Мне очень понравилось . - person bakkaa; 14.01.2021
comment
Если у вас возникнут проблемы, как в первом комментарии, github.com/davidmarkclements/rfdc также сделает мелкую копию, вам нужно включить опцию proto: true, - person Dmitry; 08.03.2021
comment
Я снова и снова чесал голову отладки, и оказалось, что объект File не копируется с помощью этого метода, и он удалялся во время deepclone. - person Junaid; 26.04.2021
comment
@GabrielHautclocq: 1. Предлагаемое решение, с которым вы боретесь, относится к ES6 (мелкая копия). Выделенное вами поведение относится к глубокой копии. Неглубокая копия копирует только значения для непосредственного уровня объекта. Если некоторые значения сами по себе не атомарны, они будут скопированы по ссылке. 2. Название ES6 НЕ вводит в заблуждение. ES6 вводит деструктуризацию, как в {...A1}, что лучше любого решения на основе for оползня. У меня есть случай объекта, подобного словарю Python, где все значения являются логическими. Альтернатива мелкой {...obj} работает как шарм. - person Ricardo; 27.04.2021
comment
@Ricardo Конечно, вы можете увидеть историю ответа, чтобы увидеть, что (мелкая копия) была добавлена ​​после ES6, после того, как я написал свой комментарий. Теперь более ясно, что это неглубокая копия. - person Gabriel Hautclocq; 28.04.2021

Оцените этот тест: 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.

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

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

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

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

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

Обновление для ES6 < / сильный>

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

Object.assign({}, obj);
person Community    schedule 17.03.2011
comment
Обратите внимание, что в вашем тесте есть 2 ошибки: во-первых, он сравнивает некоторое поверхностное клонирование (lodash _.clone и Object.assign) с некоторым глубоким клонированием (JSON.parse(JSON.stringify())). Во-вторых, он говорит о глубоком клоне для lodash, но вместо этого делает мелкий клон. - person papillon; 15.02.2021

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

var newObject = JSON.parse(JSON.stringify(oldObject));
person Community    schedule 04.01.2011
comment
У объектов есть свойства, а не переменные. ;-) - person RobG; 01.10.2020
comment
функции и даты, а также - person vsync; 24.10.2020

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

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

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

Модуль v8 в Node.js в настоящее время (начиная с Node 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;
};

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

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

person Jeremy    schedule 06.06.2012
comment
Это так неправильно! Этот API не предназначен для использования таким образом. - person Fardin K.; 01.08.2014
comment
Как человек, реализовавший pushState в Firefox, я испытываю странную смесь гордости и отвращения от этого взлома. Молодцы ребята. - person Justin L.; 14.08.2014
comment
Взлом pushState или Notification не работает для некоторых типов объектов, таких как Function - person Shishir Arora; 03.07.2019
comment
@ShishirArora Вы правы, я только что попробовал, выдает исключение Uncaught DOMException: объект не может быть клонирован. Это также верно для взлома уведомлений. - person ADJenks; 11.07.2020

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

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;
}
person Community    schedule 23.09.2008

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

Метод 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;
    }
  });
}
person Community    schedule 15.12.2015
comment
Это не рекурсивное копирование, поэтому на самом деле не предлагает решения проблемы клонирования объекта. - person mwhite; 08.03.2016
comment
Этот метод работал, хотя я тестировал несколько, и _.extend ({}, (obj)) был НАМНОГО быстрее всех: в 20 раз быстрее, чем JSON.parse, и на 60% быстрее, чем, например, Object.assign. Он неплохо копирует все подобъекты. - person Nico; 09.05.2016
comment
@mwhite есть разница между clone и deep-clone. Этот ответ на самом деле клонирует, но не глубоко клонирует. - person Meirion Hughes; 08.06.2016
comment
вопрос был о рекурсивных копиях. Object.assign, как и данное настраиваемое назначение, не копирует рекурсивно - person johannes_lalala; 04.03.2021

Код:

// 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);
person Community    schedule 25.06.2009
comment
Я не обрабатываю круглые структуры - person Gershy; 27.04.2021

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

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;
}
person Community    schedule 11.12.2009
comment
Пробуем: var a = {b: 1, c: 3, d: {a: 10, g: 20, h: {today: new Date ()}}}; У меня не работает. Но Object.assign({}, a) сделал. - person iMartin; 08.04.2021
comment
Хуже того, попробуйте let o = {}; o.o = o; cloneObject(o); - person Gershy; 27.04.2021

Глубокое копирование объектов в 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. Использование ссылки _.cloneDeep в Lo-Dash, 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 ссылка 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 1–3 http://jsben.ch/KVQLd Производительность Глубокое копирование объектов в JavaScript

person Community    schedule 08.08.2018
comment
Эй, твой последний пример неверен. На мой взгляд, для неправильного примера вы должны использовать _clone, а не _cloneDeep. - person kenanyildiz90; 10.12.2019
comment
Этот созданный метод (2.) не будет работать с массивами, не так ли? - person Toivo Säwén; 18.12.2019
comment
Метод № 2 уязвим для загрязнения прототипа, подобно тому, что случилось с defaultsDeep lodash. Он не должен копировать, если (i === '__proto__'), и не должен копировать, если (i === 'constuctor' && typeof obj[i] === 'function'). - person Frank Fajardo; 07.03.2021

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

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

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

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

person Community    schedule 18.09.2014
comment
В цикле for-in вы должны использовать hasOwnProperty, чтобы исключить унаследованные свойства. Я использую (возможно, даже быстрее) простой цикл for Object.keys. - person mikiqex; 29.06.2021
comment
Разве в глубокой копии вы бы не захотели скопировать и унаследованные свойства? Также обратите внимание, что вызов метода hasOwnProperty приводит к снижению производительности (включение и отключение вызова функции из стека и выполнение кода метода) для каждой клавиши. - person tim-montague; 30.06.2021

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] = deepCopyObj(obj[i]);
            }
            return copy;
        }
        if (obj instanceof Object) {
            var copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]);
            }
            return copy;
        }
        throw new Error("Unable to copy obj this object.");
    }
  1. ES5 +, используя JSON.parse и JSON.stringify.
    var  deepCopyObj = JSON.parse(JSON.stringify(obj));
  1. AngularJs:
    var  deepCopyObj = angular.copy(obj);
  1. jQuery:
    var deepCopyObj = jQuery.extend(true, {}, obj);
  1. UnderscoreJs и Loadash:
    var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

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

person Community    schedule 03.04.2017
comment
Спасибо за jQuery, отлично! - person tim; 02.06.2021

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

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

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

person Community    schedule 17.10.2012

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

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
person Community    schedule 24.09.2011

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

var newObject = _.clone(oldObject);
person Community    schedule 15.12.2011

Вот версия ответа 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();
    };
}
person Community    schedule 11.11.2012

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
person Community    schedule 21.08.2015

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

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

var newObject = object(oldObject);

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


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

Это полифилл для 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
person Community    schedule 06.10.2010

В Lodash есть отличный метод _.cloneDeep (value):

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
person Community    schedule 22.06.2013

Неглубокая копия однострочника (ECMAScript 5th edition):

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

И мелкая однострочная копия (6-е издание ECMAScript, 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
person Community    schedule 04.07.2012

Кажется, что еще не существует идеального оператора глубокого клонирования для объектов, подобных массиву. Как показано в приведенном ниже коде, jQuery cloner Джона Ресига превращает массивы с нечисловыми свойствами в объекты, которые не являются массивами, а JSON cloner 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)
person Community    schedule 17.10.2010

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

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

person Community    schedule 13.05.2016
comment
или его можно использовать так же, как jQiery extend: angular.extend({},obj); - person Galvani; 21.09.2016
comment
@Galvani: Следует отметить, что jQuery.extend и angular.extend - мелкие копии. angular.copy - это глубокая копия. - person Dan Atkinson; 15.10.2016

У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект 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.

Этот код не только краток, но и очень удобен для чтения. Довольно легко расширить.

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

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

person Community    schedule 06.05.2013

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

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
person Community    schedule 03.04.2011

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

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(obj), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v)
    return v;
  })
}

// usage:
var original = {
 a: [1, null, undefined, 0, {a:null}, new Date()],
 b: {
   c(){ return 0 }
 }
}

var cloned = clone(original)

console.log(cloned)

person Community    schedule 29.10.2015
comment
Не совсем 100% клон - person vsync; 24.10.2020

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

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

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

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

person Community    schedule 11.10.2017
comment
это забавно, но когда я провожу ваши тесты, он на самом деле подсказал мне, что метод 1 самый медленный - person Antoniossss; 26.04.2018
comment
как и я, блок 1 самый низкий! - person SPG; 05.12.2018
comment
Единственное решение, которое сработало для меня! Пришлось глубоко клонировать объект, содержащий другие объекты со свойствами функции. Идеально. - person Phoenix; 12.03.2021
comment
Почему вы устанавливаете obj['isActiveClone'] = null, а затем удаляете его? А почему бы тебе не позвонить obj.hasOwnProperty(key)? - person Aykut Kllic; 16.04.2021

Только когда вы можете использовать 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;
}
person Community    schedule 22.10.2015

Я не согласен с ответом, набравшим наибольшее количество голосов, здесь. Рекурсивное глубокое клонирование намного быстрее, чем упомянутый подход 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
}
person Community    schedule 18.06.2017
comment
Мне понравился этот подход, но он не обрабатывает даты должным образом; подумайте о добавлении чего-то вроде if(o instanceof Date) return new Date(o.valueOf()); после проверки на null ` - person Luis; 22.08.2017
comment
Сбои при циклических ссылках. - person Harry; 18.03.2018
comment
В последней стабильной версии Firefox это намного дольше, чем другие стратегии по этой ссылке Jsben.ch, на порядок или больше. Это побеждает других в неверном направлении. - person WBT; 14.01.2019

Вот комплексный метод 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;
};
person Community    schedule 23.07.2012
comment
Он преобразует примитивы в объекты-оболочки, что в большинстве случаев не является хорошим решением. - person Danubian Sailor; 01.08.2014
comment
@DanubianSailor - я не думаю, что это так ... похоже, он сразу возвращает примитивы с самого начала и, похоже, не делает с ними ничего, что могло бы превратить их в объекты-оболочки по мере их возврата. - person Jimbo Jonny; 02.02.2016

AngularJS

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

var newObject = angular.copy(oldObject);
person Community    schedule 14.09.2016

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

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

let a = clone(b)
person Community    schedule 05.08.2016

В 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;
}
person Community    schedule 05.11.2018
comment
Это уязвимо для глобального загрязнения Объекта. Он не должен копировать prop, если (prop === 'constuctor' && typeof src[prop] === 'function') или если (prop === '__proto__') - person Frank Fajardo; 07.03.2021

Однострочное решение 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);

person Community    schedule 17.07.2016
comment
Пожалуйста, предоставьте объяснение вместе с фрагментом кода, чтобы другие, у кого возник аналогичный вопрос, могли легко понять, что происходит. В настоящее время этот вопрос находится в очереди на рассмотрение сообщений низкого качества. - person coatless; 18.07.2016
comment
Пожалуйста, отредактируйте с дополнительной информацией. Не рекомендуется использовать только код и попробовать эти ответы, потому что они не содержат доступного для поиска контента и не объясняют, почему кто-то должен это попробовать. - person Paritosh; 18.07.2016

В Lodash есть функция, которая вам это нравится.

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

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

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

person Community    schedule 04.06.2017
comment
В итоге я использовал это, поскольку JSON.parse (JSON.stringify (obj)) не сохраняет исходный прототип объекта. - person tommyalvarez; 11.07.2017
comment
Это мой ответ goto. За исключением того, что я использую слияние Lodash, синтаксис остается в некоторой степени согласованным для глубокого и неглубокого копирования. //Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo) - person RobbyD; 20.07.2017

Пример ES 2017:

let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
person Community    schedule 26.03.2018
comment
Спасибо за ответ. Я пробовал ваш подход, но, к сожалению, он не работает. Поскольку это может быть какая-то ошибка с моей стороны, я прошу вас проверить мой пример в JSFiddle и если с моей стороны будет какая-то ошибка, я проголосую за ваш ответ. - person Takeshi Tokugawa YD; 10.06.2018
comment
Когда я запускаю вашу скрипку, я получаю { foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3} и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}. Разве это не то, чего вы ожидаете? - person codeMonkey; 10.06.2018
comment
то, что вы вставили, - это то, что я ожидал. Я не понимаю почему, но я вижу fooBaz: 44 для testObj2 и testObj3 в консоли ... (снимок экрана) - person Takeshi Tokugawa YD; 10.06.2018
comment
Это не глубокая копия, а мелкая копия. @GurebuBokofu - person Nikita Malyschkin; 17.01.2019

Ответов много, но ни один из них не дал желаемого эффекта. Я хотел использовать возможности глубокой копии 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;
        }
    }
}
person Community    schedule 29.07.2013

Обычно я использую var newObj = JSON.parse( JSON.stringify(oldObje) );, но вот более правильный способ:

var o = {};

var oo = Object.create(o);

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

Смотрите устаревшие браузеры!

person Community    schedule 24.04.2014
comment
Второй способ требует прототипа, я предпочитаю первый способ, даже если он не самый лучший по производительности из-за того, что вы можете использовать его с большим количеством браузеров и с Node JS. - person Hola Soy Edu Feliz Navidad; 03.05.2014
comment
Это круто, но предположим, что у o есть свойство a. Теперь oo.hasOwnProperty ('a')? - person user420667; 30.03.2016
comment
No-o по сути добавлен как прототип oo. Скорее всего, это не будет желаемым поведением, поэтому 99,9% методов serialize (), которые я пишу, используют подход JSON, упомянутый выше. Я в основном всегда использую JSON, и есть другие предостережения при использовании Object.create. - person Cody; 30.03.2016
comment
Нет, посмотрите этот код! Object.create не обязательно создавать копию объекта, вместо этого он использует более старый объект в качестве прототипа для клона. - person 16kb; 22.06.2018

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

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

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

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};
person Community    schedule 24.06.2011

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

person Community    schedule 24.08.2014
comment
Вы, наверное, имели в виду obj2 = Object.assign({}, obj1). Ваш текущий код эквивалентен obj2 = obj1. - person Oriol; 25.01.2015
comment
Это мелкий клон. const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456; сейчас o1.a.deep === 456 тоже. - person Josh from Qaribou; 06.02.2017
comment
Object.assign() не предназначен для клонирования вложенных объектов. - person Redu; 05.04.2017
comment
Вау, еще один бесполезный ответ. Взято из MDN developer.mozilla.org/en -US / docs / Web / JavaScript / Reference /: Warning for Deep Clone - For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value. - person basickarl; 21.04.2017

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

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

  }
  return newObject;
}

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

person Community    schedule 09.09.2017

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

person Community    schedule 19.06.2018
comment
Псевдокодом будет: для каждого ключа присвоить его значение тому же ключу в новом объекте (неглубокая копия). Однако, если значение имеет тип Object (неглубокое копирование невозможно), функция рекурсивно вызывает себя со значением в качестве аргумента. - person Parabolord; 15.08.2018
comment
Жаль, что это не работает правильно, когда значение представляет собой массив. Но не должно быть слишком сложно изменить, чтобы заставить его работать в этом случае. - person zenw0lf; 25.08.2019
comment
TypeError: невозможно прочитать свойство 'constructor' неопределенного значения - person medBouzid; 19.10.2020

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // If the function hasn't returned already, we're confident that
    // |obj| is a plain object, created by {} or constructed with new Object
    return true;
}
person Community    schedule 16.06.2014
comment
Вы можете добавить || 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"} - person AymKdn; 26.05.2017
comment
В моем случае я действительно ожидал перезаписать существующие элементы, поэтому текущее поведение было правильным для этого использования. Но спасибо, что добавили это полезное предложение. - person weeger; 16.06.2017

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

person Community    schedule 01.01.2016
comment
Рекурсия хвостового вызова на самом деле очень эффективна в большинстве реализаций JavaScript и требует оптимизации в ES6. - person rich remer; 13.02.2016
comment
Привет, я тогда провел небольшой тест, и когда целевой объект станет сложным, стек вызовов легко переполнится, хотя я не делал никаких заметок, надеюсь, в es6 это будет большая операция. - person Bodhi Hu; 22.04.2016
comment
Стек легко переполнится, вероятно, из-за циклической ссылки. - person Joe Yichong; 11.01.2017

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

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

const testObj = {
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "chance": "guid"
    },
    "emailAddr": {
      "type": "string",
      "chance": {
        "email": {
          "domain": "fake.com"
        }
      },
      "pattern": "[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))

person Community    schedule 15.10.2017
comment
Объект тура test слишком наивен. в нем должны быть undefined, функции, даты и null, а не просто набор строк - person vsync; 24.10.2020

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

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

person Community    schedule 16.07.2018

Мой сценарий был немного другим. У меня был объект с вложенными объектами, а также с функциями. Следовательно, Object.assign() и JSON.stringify() не были решением моей проблемы. Для меня тоже не подходило использование сторонних библиотек.

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

let deepCopy = (target, source) => {
    Object.assign(target, source);
    // check if there's any nested objects
    Object.keys(source).forEach((prop) => {
        /**
          * assign function copies functions and
          * literals (int, strings, etc...)
          * except for objects and arrays, so:
          */
        if (typeof(source[prop]) === 'object') {
            // check if the item is, in fact, an array
            if (Array.isArray(source[prop])) {
                // clear the copied referenece of nested array
                target[prop] = Array();
                // iterate array's item and copy over
                source[prop].forEach((item, index) => {
                    // array's items could be objects too!
                    if (typeof(item) === 'object') {
                        // clear the copied referenece of nested objects
                        target[prop][index] = Object();
                        // and re do the process for nested objects
                        deepCopy(target[prop][index], item);
                    } else {
                        target[prop].push(item);
                    }
                });
            // otherwise, treat it as an object
            } else {
                // clear the copied referenece of nested objects
                target[prop] = Object();
                // and re do the process for nested objects
                deepCopy(target[prop], source[prop]);
            }
        }
    });
};

Вот тестовый код:

let a = {
    name: 'Human', 
    func: () => {
        console.log('Hi!');
    }, 
    prop: {
        age: 21, 
        info: {
            hasShirt: true, 
            hasHat: false
        }
    },
    mark: [89, 92, { exam: [1, 2, 3] }]
};

let b = Object();

deepCopy(b, a);

a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];

console.log(a); // updated props
console.log(b);

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

person Community    schedule 19.05.2019

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

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

Используйте 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;
}
person Community    schedule 19.06.2015
comment
Отличный ответ! Я думаю, что это один из немногих способов сохранить нетронутыми сеттеры и геттеры. Это решило мою проблему. Спасибо! (см .: stackoverflow.com/questions/33207028/) - person lepe; 20.10.2015
comment
Разве вы не хотели бы использовать clone = Object.create(Object.getPrototypeOf(source)) вместо наследования свойств, которые вы также перезаписываете? - person Jeremy; 07.04.2016
comment
Интересно. Однако использование getPrototypeOf на Array превращает его индексы в ключи нового Object. - person Steven Vachon; 11.04.2016

Клонирование объекта с использованием современного 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-to-JavaScript, такого как Babel, для вывода ECMAScript 5 вашего кода JavaScript.

Как на что указал @ jim-hall, это лишь поверхностная копия. Свойства свойств копируются как ссылка: изменение одного из них приведет к изменению значения в другом объекте / экземпляре.

person Community    schedule 23.03.2016
comment
Это не касается глубоких слияний. gist.github.com/jimbol/5d5a3e3875c34abcf60a - person Jim Hall; 25.03.2016
comment
Вау, это такой неправильный ответ. Оба ваших метода делают мелкую копию одного уровня. Любой, кто смотрит на этот ответ, продолжайте. - person basickarl; 21.04.2017

Для справки в будущем можно использовать этот код

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

}

E.g

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}}
person Community    schedule 21.11.2016
comment
это не обрабатывает массивы, которые также являются объектами. - person Soldeplata Saketos; 30.10.2017

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

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

person Community    schedule 05.04.2017

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

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}
person Community    schedule 20.01.2018
comment
Постойте, 5 сторонников, как это работает? Я сам забыл об этом, и теперь, когда прошло полтора года, это выглядит нелогично. - person Константин Ван; 27.08.2019
comment
Не знаю, что он должен делать, я в замешательстве: s - person Sebi; 22.08.2020
comment
Promise.resolve(value) разрешает клонированный value? Я сомневаюсь в этом, мимо меня. - person Константин Ван; 23.08.2020

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

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

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

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

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

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

person Community    schedule 26.02.2018
comment
Все это уже преобразовано либо в ответах, либо в комментариях. Я бы проголосовал за это, если бы вы дали уникальные примеры кода для каждого из них. - person Jack Giffin; 07.04.2018

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

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

let objClone = { ...obj };

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

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

person Community    schedule 24.06.2018
comment
обновление e в objClone по-прежнему будет обновлять e в obj. Это пока лишь поверхностная копия. Вопрос явно требует глубокого клона. - person Taugenichts; 27.06.2018
comment
@Taugenichts ... ты это тестировал? Метод работает отлично. Раздел Spread_syntax Spread in object literals - person mickro; 27.06.2018
comment
да, я это тестировал. запустите этот код: objClone.e [4] = 5; console.log (obj.e); Вы увидите, что obj.e обновляется - person Taugenichts; 27.06.2018
comment
Поскольку оба хранятся в разных местах, это просто означает, что это, по крайней мере, неглубокая копия. Посмотрите, где хранятся obj.e и objClone.e; вы обнаружите, что они хранятся в одном месте. - person Lupus Ossorum; 30.06.2018
comment
Большое спасибо, ребята @ LupusOssorum @Taugenichts, что указали на это. Я сам это проверил и выяснил, что вы здесь определили. Но знаете ли вы, почему массив до сих пор не меняет память, хотя ECMA2018 может похвастаться этим как функцией. - person Vikram K; 10.07.2018
comment
Из документации mozilla в разделе Распространение в литералах объектов: поверхностное клонирование (за исключением прототипа) или слияние объектов теперь возможно с использованием более короткого синтаксиса, чем Object.assign (). - developer.mozilla.org/en-US/docs / Web / JavaScript / Reference / - person Taugenichts; 13.09.2018

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

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

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

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

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

var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
person Community    schedule 28.05.2019
comment
Далеко не лучший. возможно, для простых предметов. - person vsync; 24.10.2020

Это мое решение без использования какой-либо библиотеки или встроенной функции javascript.

function deepClone(obj) {
  if (typeof obj !== "object") {
    return obj;
  } else {
    let newObj =
      typeof obj === "object" && obj.length !== undefined ? [] : {};
    for (let key in obj) {
      if (key) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  }
}
person Community    schedule 03.08.2019
comment
Осторожно ... const o = {}; o.a = o; deepClone(o); - ›ошибка рекурсии. - person Ian; 21.08.2019

Требуются новые браузеры, но ...

Давайте расширим собственный объект и получим настоящий .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"}
person Community    schedule 04.08.2015
comment
Изменение прототипов обычно считается плохой практикой, за исключением прокладок. - person Josh from Qaribou; 06.02.2017

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

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

person Community    schedule 30.10.2016
comment
полностью терпит неудачу с помощью простого теста [1,2] - person vsync; 24.10.2020

Если ваш объект вложен и содержит объект данных, другой структурированный объект или какой-либо объект свойства и т. Д., То использование 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 без каких-либо ссылок.

person Community    schedule 23.08.2018

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

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

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

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

person Community    schedule 10.10.2018

С предложением нового метода Object.fromEntries (), который поддерживается в более новых версиях некоторых браузеров (ссылка). Я хочу внести свой вклад в следующий рекурсивный подход:

const obj = {
  key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
  key2: {key21: "key21", key22: "key22"},
  key3: "key3",
  key4: [1,2,3, {key: "value"}]
}

const cloneObj = (obj) =>
{
    if (Object(obj) !== obj)
       return obj;
    else if (Array.isArray(obj))
       return obj.map(cloneObj);

    return Object.fromEntries(Object.entries(obj).map(
        ([k,v]) => ([k, cloneObj(v)])
    ));
}

// Clone the original object.
let newObj = cloneObj(obj);

// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";

// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

person Community    schedule 16.04.2019

Поскольку этому вопросу уделяется много внимания, и на него есть ответы в отношении встроенных функций, таких как 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 - сохранить глубокое клонирование 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);
person Community    schedule 16.08.2017

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?

person Community    schedule 05.02.2019

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

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

    var clone = {};

    keys.forEach(function(key, i) {
        clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];
    });

    return clone;
}

Примечание. Этот метод не обязательно создает мелкие копии, но он копирует только с глубиной одного внутреннего объекта, что означает, что когда вам дается что-то вроде {a: {b: {c: null}}}, он будет клонировать только те объекты, которые находятся непосредственно внутри них, поэтому deepClone(a.b).c технически является ссылкой на a.b.c, а deepClone(a).b является клоном, не ссылкой.

person Community    schedule 05.02.2019