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

avatar
jschrab
23 сентября 2008 в 16:26
2324177
67
5174

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

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

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

Источник
Heavy Gray
22 марта 2012 в 14:08
566

Эвал не зло. Плохое использование eval. Если вы боитесь его побочных эффектов, вы неправильно его используете. Побочные эффекты, которых вы боитесь, - это причины для его использования. Кстати, действительно ли кто-нибудь ответил на ваш вопрос?

b01
11 марта 2013 в 22:25
15

Клонирование объектов - непростая задача, особенно с пользовательскими объектами из произвольных коллекций. Вероятно, поэтому нет готового способа сделать это.

user56reinstatemonica8
8 сентября 2014 в 13:37
12

eval() - вообще плохая идея, потому что многие оптимизаторы движка Javascript должны отключаться при работе с переменными, которые устанавливаются через eval. Просто наличие eval() в вашем коде может привести к снижению производительности.

John Slegers
21 февраля 2016 в 18:21
2

Возможный дубликат Самый элегантный способ клонировать объект JavaScript

EscapeNetscape
17 октября 2016 в 09:58
1

вот сравнение производительности наиболее распространенных типов объектов клонирования: jsben.ch/#/t917Z

oriadam
24 мая 2017 в 13:06
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}

Navid
6 июля 2019 в 07:07
0

Сообщество React представило помощник по неизменяемости

Ответы (67)

avatar
Dan Dascalescu
28 мая 2021 в 23:04
5193

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

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

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

Если вы не используете Date функции, undefined, Infinity, регулярные выражения, карты, наборы, большие двоичные объекты, списки файлов, 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()

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

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

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

  • lodash - cloneDeep; может быть импортирован отдельно через модуль lodash.clonedeep и, вероятно, ваш лучший выбор, если вы еще не используете библиотеку, которая предоставляет функцию глубокого клонирования
  • 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
Gabriel Hautclocq
30 сентября 2020 в 12:54
75

Остерегаться! 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!

medBouzid
19 октября 2020 в 14:12
0

@GabrielHautclocq какое решение в этом случае?

Utku
24 октября 2020 в 22:37
0

@GabrielHautclocq Вы только что узнали, что означает «неглубокая копия». "@medBouzid" Решение состоит в "глубоком копировании" объекта вместо поверхностного копирования.

Gabriel Hautclocq
25 октября 2020 в 07:47
0

Я просто продемонстрировал на примере, что нельзя использовать Object.assign для глубокого клонирования объекта.

cst1992
29 октября 2020 в 09:56
0

Object.assign() действительно бесполезен, если ваш объект для копирования содержит в нем массивы, особенно массивы объектов. Просто выяснил это на собственном горьком опыте ...

Unicornist
15 декабря 2020 в 13:21
6

@Gabriel Hautclocq это потому, что A.b или B.b оба относятся к одному и тому же объекту в памяти. если A имеет свойство с не объектным значением (например, числа или строки), оно будет скопировано обычным образом. Но когда копируется свойство, содержащее значение объекта, оно копируется по ссылке, а не по значению. Также имейте в виду, что массив - это объект в JS. доказательство: typeof [] == 'object' && [] instanceof Array

Gabriel Hautclocq
15 декабря 2020 в 13:58
23

@Unicornist Да, и именно поэтому Object.assign не отвечает на вопрос: «Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript?». Так что, по крайней мере, его НЕ следует представлять как решение ES6 для глубокого клонирования. Название «ES6» вводит в заблуждение, по крайней мере, его следует изменить, чтобы отразить, что это не метод глубокого клонирования. Слово «мелкий» легко упустить из виду, и многие люди просто берут самое простое решение, которое они находят в Stack Overflow, не читая все подряд. Опасно полагаться на Object.assign при клонировании объекта. Отсюда и мое замечание.

bakkaa
14 января 2021 в 16:02
2

Я использовал библиотеку под названием действительно быстрый глубокий клон: github.com/davidmarkclements/rfdc Мне очень понравилось.

Dmitry
8 марта 2021 в 10:48
1

Если у вас возникли проблемы, как в первом комментарии, github.com/davidmarkclements/rfdc также сделает «мелкую копию», вам необходимо включить опцию proto: true,

Junaid
26 апреля 2021 в 12:13
0

Я снова и снова чесал голову отладки, и оказалось, что объект File не копируется с помощью этого метода, и он удалялся во время deepclone.

Ricardo
27 апреля 2021 в 08:34
0

@GabrielHautclocq: 1. Предлагаемое решение, с которым вы боретесь, относится к ES6 ( мелкая копия). Выделенное вами поведение относится к глубокой копии . Неглубокая копия копирует только значения для непосредственного уровня объекта. Если некоторые значения сами по себе не атомарны, они будут скопированы по ссылке. 2. Заголовок ES6 НЕ вводит в заблуждение. ES6 вводит деструктуризацию, как в {...A1}, что лучше любого решения на основе for при обвале. У меня есть случай объекта, подобного словарю Python, где все значения являются логическими. Мелкая альтернатива {...obj} работает как шарм.

Gabriel Hautclocq
28 апреля 2021 в 09:39
1

@Ricardo Конечно, вы можете увидеть историю ответа, чтобы увидеть, что "(мелкая копия)" была добавлена ​​после "ES6" после того, как я написал свой комментарий. Теперь более ясно, что это неглубокая копия.

avatar
Alireza
4 марта 2021 в 02:01
68

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

Надеюсь на эту помощь ...

tim
2 июня 2021 в 10:11
0

Спасибо за jQuery, отлично!

avatar
Mayur Agarwal
7 декабря 2020 в 10:46
16

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

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

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

и делимся результатами: enter image description here Ссылки: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

Antoniossss
26 апреля 2018 в 08:30
0

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

SPG
5 декабря 2018 в 01:08
0

как и я, блок 1 самый низкий!

Phoenix
12 марта 2021 в 17:22
0

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

Aykut Kllic
16 апреля 2021 в 07:50
0

Почему вы устанавливаете obj['isActiveClone'] = null, а затем удаляете его? А почему бы вам не позвонить по номеру obj.hasOwnProperty(key)?

avatar
Buzinas
24 октября 2020 в 13:07
14

Для людей, которые хотят использовать версию 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)
vsync
24 октября 2020 в 13:07
0

Не совсем 100% клон

avatar
Eugene Tiurin
20 июня 2020 в 09:12
160

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

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

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

Метод Object.assign () используется для копирования значений всех перечисляемых собственных свойств из одного или нескольких исходных объектов в целевой объект.

Подробнее ...

полифил для поддержки старых браузеров:

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;
    }
  });
}
mwhite
8 марта 2016 в 19:56
92

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

Nico
9 мая 2016 в 19:57
5

Этот метод работал, хотя я тестировал несколько, и _.extend ({}, (obj)) был НАМНОГО быстрее всех: в 20 раз быстрее, чем JSON.parse, и на 60% быстрее, чем, например, Object.assign. Он неплохо копирует все подобъекты.

Meirion Hughes
8 июня 2016 в 12:08
12

@mwhite есть разница между clone и deep-clone. Этот ответ на самом деле клонирует, но не глубоко клонирует.

johannes_lalala
4 марта 2021 в 01:52
0

вопрос был о рекурсивных копиях. Object.assign, как и данное настраиваемое назначение, не копирует рекурсивно

avatar
Tính Ngô Quang
20 ноября 2019 в 04:52
90

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

1. Использование JSON.parse (JSON.stringify (объект));

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 ссылка 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 Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Performance Deep copying objects in JavaScript

kenanyildiz90
10 декабря 2019 в 07:20
1

Эй, твой последний пример неверен. На мой взгляд, для неправильного примера вы должны использовать _clone, а не _cloneDeep.

Toivo Säwén
18 декабря 2019 в 14:06
0

Этот созданный метод (2.) не будет работать с массивами, не так ли?

Frank Fajardo
7 марта 2021 в 08:46
0

Метод № 2 уязвим для загрязнения прототипа, аналогично тому, что случилось с defaultsDeep lodash. Он не должен копировать, если (i === '__proto__'), и не должен копировать, если (i === 'constuctor' && typeof obj[i] === 'function').

avatar
Corban Brook
20 ноября 2019 в 04:31
2370

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

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

JSON.parse(JSON.stringify(obj))

- самый медленный способ глубокого клонирования объекта (он медленнее, чем jQuery.extend с флагом deep, установленным на 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() не преобразует <60568568568568568 обратно в <60500. объект. Подробнее см. Этот ответ.

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

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

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

Object.assign({}, obj);
papillon
15 февраля 2021 в 14:22
3

Обратите внимание, что в вашем стенде есть 2 ошибки: во-первых, он сравнивает некоторое поверхностное клонирование (lodash _.clone и Object.assign) с некоторым глубоким клонированием (JSON.parse(JSON.stringify())). Во-вторых, он говорит «глубокий клон» для lodash, но вместо этого делает неглубокий клон.

avatar
3 августа 2019 в 16:36
3

Это мое решение без использования какой-либо библиотеки или встроенной функции 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;
  }
}
Ian
21 августа 2019 в 17:23
0

Осторожно ... const o = {}; o.a = o; deepClone(o); -> ошибка рекурсии.

avatar
28 мая 2019 в 06:17
3

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);
vsync
24 октября 2020 в 13:15
0

Далеко не «лучший». возможно, для простых предметов.

avatar
Kamyar
20 мая 2019 в 10:33
4

Мой сценарий был немного другим. У меня был объект с вложенными объектами, а также с функциями. Следовательно, 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);

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

avatar
Shidersz
16 апреля 2019 в 16:00
2

С предложением нового метода 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;}
avatar
Eternal Darkness
12 февраля 2019 в 04:00
0

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

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 является клоном, не ссылкой .

.
avatar
shakthi nagaraj
5 февраля 2019 в 13:50
1
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?

avatar
5 ноября 2018 в 09:43
8

В 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;
}
Frank Fajardo
7 марта 2021 в 08:49
0

Это уязвимо для глобального загрязнения Объекта. Он не должен копировать prop, если (prop === 'constuctor' && typeof src[prop] === 'function') или если (prop === '__proto__')

avatar
shunryu111
10 октября 2018 в 10:17
2

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

avatar
ConroyP
27 сентября 2018 в 08:59
349

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

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;
}
avatar
23 августа 2018 в 09:40
2

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

avatar
16 июля 2018 в 07:58
4

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

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

avatar
Vikram K
29 июня 2018 в 21:21
3

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

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

let objClone = { ...obj };

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

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

Taugenichts
27 июня 2018 в 14:59
1

обновление e в objClone по-прежнему будет обновлять e в obj. Это пока лишь поверхностная копия. Вопрос явно требует глубокого клона.

mickro
27 июня 2018 в 17:06
0

@Taugenichts ... ты это тестировал? Метод работает отлично. Spread_syntax Spread in object literals раздел

Taugenichts
27 июня 2018 в 17:35
1

да, я это тестировал. запустите этот код: objClone.e [4] = 5; console.log (obj.e); Вы увидите, что obj.e обновляется

Lupus Ossorum
29 июня 2018 в 21:24
2

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

Vikram K
10 июля 2018 в 09:29
1

Большое спасибо, ребята @ LupusOssorum @Taugenichts, что указали на это. Я сам это проверил и выяснил, что вы здесь определили. Но знаете ли вы, почему массив до сих пор не изменяет память, хотя ECMA2018 может похвастаться этим как функцией.

avatar
19 июня 2018 в 22:06
5

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

Parabolord
15 августа 2018 в 20:48
2

Псевдокодом будет: для каждого ключа присвоить его значение тому же ключу в новом объекте (неглубокая копия). Однако, если значение имеет тип Object (неглубокое копирование невозможно), функция рекурсивно вызывает себя со значением в качестве аргумента.

zenw0lf
24 августа 2019 в 22:30
1

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

medBouzid
19 октября 2020 в 15:08
0

TypeError: невозможно прочитать свойство 'constructor' неопределенного

avatar
26 марта 2018 в 17:42
6

Пример ES 2017:

let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
Takeshi Tokugawa YD
10 июня 2018 в 03:39
0

Спасибо за ответ. Я пробовал ваш подход, но, к сожалению, он не работает. Поскольку это может быть какая-то ошибка с моей стороны, я прошу вас проверить мой пример в JSFiddle, и если это будет какая-то ошибка с моей стороны, я проголосую за ваш ответ.

codeMonkey
10 июня 2018 в 03:50
0

Когда я запускаю вашу скрипку, я получаю { foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3} и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}. Разве это не то, чего вы ожидаете?

Takeshi Tokugawa YD
10 июня 2018 в 03:55
0

то, что вы вставили, - это то, что я ожидал. Я не понимаю почему, но я вижу fooBaz: 44 для testObj2 и testObj3 в консоли ... (скриншот)

Nikita Malyschkin
17 января 2019 в 06:25
2

Это не глубокая копия, а мелкая копия. @GurebuBokofu

avatar
tfmontague
20 марта 2018 в 15:11
84

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

  • Переназначение "=" (строковые массивы, только числовые массивы)
  • Срез (строковые массивы, только числовые массивы)
  • Объединение (только строковые массивы, только числовые массивы)
  • Пользовательская функция: цикл for или рекурсивное копирование
  • $ .extend из jQuery
  • JSON.parse (только строковые массивы, числовые массивы, массивы объектов)
  • Underscore.js _.clone (строковые массивы, только числовые массивы)
  • _.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

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

mikiqex
29 июня 2021 в 06:19
1

В цикле for-in вы должны использовать hasOwnProperty, чтобы исключить унаследованные свойства. Я использую (возможно, даже быстрее) простой цикл для Object.keys.

tim-montague
30 июня 2021 в 17:47
1

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

avatar
26 февраля 2018 в 04:05
3

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

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

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

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

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

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

Jack Giffin
7 апреля 2018 в 00:24
0

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

avatar
20 января 2018 в 20:26
3

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

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}
Константин Ван
27 августа 2019 в 16:13
2

Постойте, 5 сторонников, как это работает? Я сам забыл об этом, и теперь, когда прошло полтора года, это выглядит нелогично.

Sebi
22 августа 2020 в 12:27
0

Не знаю, что он должен делать, я в замешательстве: s

Константин Ван
22 августа 2020 в 22:41
0

Разрешает ли Promise.resolve(value) клонированный value? Я сомневаюсь в этом, мимо меня.

avatar
Dima
17 января 2018 в 16:27
12
// 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;
};
avatar
opensas
31 декабря 2017 в 12:59
23

Lodash имеет красивый метод _.cloneDeep (value):

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
avatar
15 октября 2017 в 20:01
4

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

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

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

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

console.log(makeDeepCopy(testObj))
vsync
24 октября 2020 в 13:13
0

Тур test объект слишком наивен . он должен иметь некоторые undefined, функции, даты и null, а не просто набор строк

avatar
9 сентября 2017 в 15:08
5

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

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

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

avatar
16 августа 2017 в 06:16
1

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

1. esclone

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

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

import esclone from "esclone";

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

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

const rockyClone = esclone(rocky);

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

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

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

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

Пример:

var dcopy = require('deep-copy')

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

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

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

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

Пример:

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

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

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

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

console.log(arr);
avatar
18 июня 2017 в 06:34
13

Я не согласен с ответом, набравшим наибольшее количество голосов. Рекурсивный глубокий клон намного быстрее , чем упомянутый подход 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
}
Luis
21 августа 2017 в 22:53
2

Мне понравился этот подход, но он не обрабатывает даты должным образом; рассмотрите возможность добавления чего-то вроде if(o instanceof Date) return new Date(o.valueOf()); после проверки на null `

Harry
18 марта 2018 в 05:19
0

Сбои при циклических ссылках.

WBT
14 января 2019 в 18:38
0

В последней стабильной версии Firefox это намного дольше, чем другие стратегии по этой ссылке Jsben.ch, на порядок или больше. Это побеждает других в неправильном направлении.

avatar
4 июня 2017 в 20:49
6

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

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

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

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

tommyalvarez
11 июля 2017 в 17:27
0

В итоге я использовал это, поскольку JSON.parse (JSON.stringify (obj)) не сохраняет исходный прототип объекта.

RobbyD
20 июля 2017 в 07:30
0

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

avatar
Barry Staes
23 мая 2017 в 12:10
3

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

Jim Hall
25 марта 2016 в 18:10
27

Это не касается глубоких слияний. gist.github.com/jimbol/5d5a3e3875c34abcf60a

basickarl
21 апреля 2017 в 09:59
16

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

avatar
5 апреля 2017 в 15:06
3

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

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);
avatar
Matt Browne
12 марта 2017 в 18:02
44

Вот версия ответа 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://coderhelper.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();
    };
}
avatar
user3071643
22 декабря 2016 в 16:48
7

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

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

let a = clone(b)
avatar
nathan rogers
1 декабря 2016 в 16:06
31

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
avatar
24 ноября 2016 в 11:15
3

class Handler {
  static deepCopy (obj) {
    if (Object.prototype.toString.call(obj) === '[object Array]') {
      const result = [];
      
      for (let i = 0, len = obj.length; i < len; i++) {
        result[i] = Handler.deepCopy(obj[i]);
      }
      return result;
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
      const result = {};
      for (let prop in obj) {
        result[prop] = Handler.deepCopy(obj[prop]);
      }
      return result;
    }
    return obj;
  }
}
avatar
21 ноября 2016 в 14:05
3

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

ES6:

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

ES5:

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

}

Например,

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

xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
Soldeplata Saketos
30 октября 2017 в 08:02
1

это не обрабатывает массивы, которые также являются объектами.

avatar
nem035
15 ноября 2016 в 16:36
6

Однострочное решение 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);
coatless
17 июля 2016 в 22:50
5

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

Paritosh
18 июля 2016 в 09:17
1

Пожалуйста, отредактируйте с дополнительной информацией. Ответы только на код и «попробуйте это» не приветствуются, потому что они не содержат доступного для поиска контента и не объясняют, почему кто-то должен «попробовать это».

avatar
SAlidadi
30 октября 2016 в 14:29
2

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

obj = {
  a: { b: { c: { d: ['1', '2'] } } },
  e: 'Saeid'
}
const Clone = function (obj) {
  
  const container = Array.isArray(obj) ? [] : {}
  const keys  = Object.keys(obj)
   
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if(typeof obj[key] == 'object') {
      container[key] = Clone(obj[key])
    }
    else
      container[key] = obj[key].slice()
  }
  
  return container
}
 console.log(Clone(obj))
vsync
24 октября 2020 в 13:19
0

полностью не выполняется с помощью простого теста [1,2]

avatar
Dan Atkinson
15 октября 2016 в 18:38
18

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

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

Galvani
21 сентября 2016 в 09:07
0

или его можно использовать так же, как расширение jQuery: angular.extend({},obj);

Dan Atkinson
15 октября 2016 в 18:41
2

@Galvani: Следует отметить, что jQuery.extend и angular.extend являются мелкими копиями. angular.copy - это глубокая копия.

avatar
14 сентября 2016 в 13:26
12

AngularJS

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

var newObject = angular.copy(oldObject);
avatar
Sean Hu
22 июля 2016 в 17:49
4

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

rich remer
13 февраля 2016 в 05:19
1

Рекурсия хвостового вызова на самом деле очень эффективна в большинстве реализаций JavaScript и требует оптимизации в ES6.

Bodhi Hu
22 апреля 2016 в 12:36
0

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

Joe Yichong
11 января 2017 в 01:16
0

Стек легко переполнится, вероятно, из-за циклической ссылки.

avatar
andrew
22 июля 2016 в 17:46
13

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

Характеристики:

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

Код:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}
avatar
Robin Whittleton
22 июля 2016 в 17:40
5

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

Oriol
25 января 2015 в 11:10
3

Вы, наверное, имели в виду obj2 = Object.assign({}, obj1). Ваш текущий код эквивалентен obj2 = obj1.

Josh from Qaribou
6 февраля 2017 в 14:21
5

Это мелкий клон. const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456; теперь тоже o1.a.deep === 456.

Redu
5 апреля 2017 в 14:31
2

Object.assign() не для клонирования вложенных объектов.

basickarl
21 апреля 2017 в 10:04
3

Вау, еще один бесполезный ответ. Взято из 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.

avatar
weeger
22 июля 2016 в 17:37
4

Это моя версия клонирования объектов. Это автономная версия метода 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;
}
AymKdn
26 мая 2017 в 10:05
1

Вы можете добавить || typeof target[name] !== "undefined" при тестировании if (target === to_add[name]) { continue; }, чтобы не перезаписывать существующие элементы target. Например, var a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a); мы ожидаем найти b => {hello:"you", foo:"bar"}, но с вашим кодом мы находим: b => {hello:"world", foo:"bar"}

weeger
16 июня 2017 в 13:01
0

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

avatar
Michael Uzquiano
22 июля 2016 в 17:33
17

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

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

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

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

avatar
user1547016
22 июля 2016 в 17:31
12

Вот комплексный метод 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;
};
Danubian Sailor
1 августа 2014 в 09:58
0

Он преобразует примитивы в объекты-оболочки, что в большинстве случаев не является хорошим решением.

Jimbo Jonny
2 февраля 2016 в 18:06
0

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

avatar
Maël Nison
22 июля 2016 в 17:27
22

Мелкая однострочная копия (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 6-е издание, 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
avatar
itsadok
22 июля 2016 в 17:26
49

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

var newObject = _.clone(oldObject);
avatar
Steve Tomlin
22 июля 2016 в 17:25
5

Это самый быстрый из созданных мной методов, который не использует прототип, поэтому он будет поддерживать 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};
avatar
protonfish
12 марта 2016 в 14:59
25

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

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

var newObject = object(oldObject);

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


EDIT :

Это полифилл для 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
avatar
4 августа 2015 в 19:38
2

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

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

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

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

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

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

        return this;
    }
});

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

Пример:

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

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

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

console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
Josh from Qaribou
6 февраля 2017 в 14:23
1

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

avatar
Steven Vachon
19 июня 2015 в 03:20
3

Используйте 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;
}
lepe
20 октября 2015 в 05:54
0

Отличный ответ! Я думаю, что это один из немногих способов сохранить нетронутыми сеттеры и геттеры. Это решило мою проблему. Спасибо! (см .: coderhelper.com/questions/33207028/…)

Jeremy
7 апреля 2016 в 17:47
0

Разве вы не хотели бы использовать clone = Object.create(Object.getPrototypeOf(source)) вместо наследования свойств, которые вы также перезаписываете?

Steven Vachon
11 апреля 2016 в 16:47
0

Интересный. Однако использование getPrototypeOf на Array превращает его индексы в ключи нового Object.

avatar
Joe
17 марта 2015 в 01:18
54

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

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
avatar
Sultan Shakir
23 октября 2014 в 23:47
516

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

var newObject = JSON.parse(JSON.stringify(oldObject));
RobG
1 октября 2020 в 00:42
12

У объектов есть свойства, а не переменные. ;-)

vsync
24 октября 2020 в 12:54
0

функции и даты , а также

avatar
Cody
12 августа 2014 в 18:44
5

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

var o = {};

var oo = Object.create(o);

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

Следите за устаревшими браузерами!

Hola Soy Edu Feliz Navidad
3 мая 2014 в 10:03
0

Для второго способа нужен прототип, я предпочитаю первый, даже если он не самый лучший по производительности из-за того, что вы можете использовать его с большим количеством браузеров и с Node JS.

user420667
29 марта 2016 в 23:54
0

Это круто, но предположим, что у o есть свойство a. Теперь есть oo.hasOwnProperty ('a')?

Cody
30 марта 2016 в 02:30
0

No-o по сути добавлен как прототип oo. Скорее всего, это не будет желаемым поведением, поэтому 99,9% методов serialize (), которые я пишу, используют подход JSON, упомянутый выше. Я в основном всегда использую JSON, и есть другие предостережения при использовании Object.create.

16kb
22 июня 2018 в 01:07
1

Нет, посмотрите этот код! Object.create не обязательно создавать копию объекта, вместо этого он использует более старый объект в качестве прототипа для клона.

avatar
gion_13
23 декабря 2013 в 05:48
3

Я думаю, что это лучшее решение, если вы хотите обобщить свой алгоритм клонирования объекта.
Его можно использовать с 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);            
}
avatar
Page Notes
13 декабря 2013 в 19:00
18

Кажется, еще не существует идеального оператора глубокого клонирования для объектов, подобных массиву. Как показано в приведенном ниже коде, средство клонирования 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)
avatar
29 июля 2013 в 02:50
5

Ответов много, но ни один из них не дал желаемого эффекта. Я хотел использовать возможности глубокой копии 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;
        }
    }
}
avatar
Alan
16 июля 2013 в 16:37
108

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

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;
}
iMartin
8 апреля 2021 в 19:58
0

Пробуем: var a = {b: 1, c: 3, d: {a: 10, g: 20, h: {today: new Date ()}}}; У меня не работает. Но Object.assign({}, a) сделал.

Gershy
27 апреля 2021 в 17:42
0

Хуже того, попробуйте let o = {}; o.o = o; cloneObject(o);

avatar
pvorb
27 марта 2013 в 08:04
61

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

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

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

avatar
Zibri
21 февраля 2013 в 15:01
67
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});
avatar
Jeremy
6 июня 2012 в 14:59
469

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

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

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

'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();
Fardin K.
31 июля 2014 в 23:34
47

Это так неправильно! Этот API не предназначен для использования таким образом.

Justin L.
14 августа 2014 в 18:37
269

Как человек, реализовавший pushState в Firefox, я испытываю странную смесь гордости и отвращения от этого взлома. Молодцы ребята.

Shishir Arora
3 июля 2019 в 20:06
1

Взлом pushState или Notification не работает для некоторых типов объектов, таких как Function

ADJenks
10 июля 2020 в 21:39
0

@ShishirArora Вы правы, я только что попробовал, выдает исключение Uncaught DOMException: объект не может быть клонирован. Это также верно для взлома уведомлений.

avatar
3 апреля 2011 в 02:08
15

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

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
avatar
Kamarey
26 марта 2011 в 14:20
109

Код:

// 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);
Gershy
27 апреля 2021 в 17:42
0

Я не обрабатываю круглые структуры

avatar
23 сентября 2008 в 16:45
22
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }