Как правильно клонировать объект JavaScript?
У меня есть объект, x
, Я хотел бы скопировать его как объект y
такой, что меняется на y
не модифицируйте x
, Я понял, что копирование объектов, полученных из встроенных объектов JavaScript, приведет к появлению дополнительных нежелательных свойств. Это не проблема, так как я копирую один из своих объектов, созданных в буквальном смысле.
Как правильно клонировать объект JavaScript?
86 ответов
Сделать это для любого объекта в JavaScript не будет просто или просто. Вы столкнетесь с проблемой ошибочного выбора атрибутов из прототипа объекта, которые следует оставить в прототипе и не копировать в новый экземпляр. Если, например, вы добавляете clone
метод для Object.prototype
, как показывают некоторые ответы, вам нужно явно пропустить этот атрибут. Но что, если есть другие дополнительные методы, добавленные к Object.prototype
или другие промежуточные прототипы, о которых вы не знаете? В этом случае вы будете копировать атрибуты, которые вы не должны, поэтому вам нужно обнаружить непредвиденные нелокальные атрибуты с помощью hasOwnProperty
метод.
Помимо неперечислимых атрибутов, вы столкнетесь с более сложной проблемой, когда попытаетесь скопировать объекты со скрытыми свойствами. Например, prototype
является скрытым свойством функции. Кроме того, на прототип объекта ссылается атрибут __proto__
, который также скрыт и не будет скопирован циклом for/in, повторяющимся по атрибутам исходного объекта. Я думаю __proto__
может быть специфичным для интерпретатора JavaScript Firefox и может отличаться в других браузерах, но вы получите представление. Не все перечисляемо. Вы можете скопировать скрытый атрибут, если знаете его имя, но я не знаю, как его обнаружить автоматически.
Еще одна проблема в поиске элегантного решения - это проблема правильной настройки наследования прототипа. Если прототип вашего исходного объекта Object
затем просто создайте новый общий объект с {}
будет работать, но если прототип источника является неким потомком Object
то вы пропустите дополнительные элементы из того прототипа, который вы пропустили, используя hasOwnProperty
фильтр, или которые были в прототипе, но не были перечисляемыми в первую очередь. Одним из решений может быть вызов исходного объекта constructor
свойство, чтобы получить начальный объект копирования, а затем скопировать атрибуты, но тогда вы все равно не получите неперечислимые атрибуты. Например, Date
Объект хранит свои данные как скрытый член:
function clone(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
var d1 = new Date();
/* Executes function after 5 seconds. */
setTimeout(function(){
var d2 = clone(d1);
alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);
Строка даты для d1
будет на 5 секунд отставать от d2
, Способ сделать один Date
так же, как другой, вызывая setTime
метод, но это специфично для Date
учебный класс. Я не думаю, что есть пуленепробиваемое общее решение этой проблемы, хотя я был бы рад ошибаться!
Когда мне пришлось реализовать общее глубокое копирование, я в итоге пошел на компромисс, предполагая, что мне нужно будет только скопировать простое Object
, Array
, Date
, String
, Number
, или же Boolean
, Последние 3 типа являются неизменяемыми, поэтому я мог выполнить поверхностное копирование и не беспокоиться о его изменении. Я также предположил, что любые элементы, содержащиеся в Object
или же Array
также будет одним из 6 простых типов в этом списке. Это может быть выполнено с помощью кода, подобного следующему:
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.");
}
Вышеприведенная функция будет работать адекватно для 6 упомянутых мною простых типов, если данные в объектах и массивах образуют древовидную структуру. То есть в объекте не более одной ссылки на одни и те же данные. Например:
// This would be cloneable:
var tree = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"right" : null,
"data" : 8
};
// This would kind-of work, but you would get 2 copies of the
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"data" : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
"left" : { "left" : null, "right" : null, "data" : 3 },
"data" : 8
};
cyclicGraph["right"] = cyclicGraph;
Он не сможет обработать какой-либо объект JavaScript, но этого может быть достаточно для многих целей, если вы не предполагаете, что он будет работать только для всего, что вы на него бросаете.
Если вы не используете функции в вашем объекте, очень простой однострочник может быть следующим:
var cloneOfA = JSON.parse(JSON.stringify(a));
Это работает для всех видов объектов, содержащих объекты, массивы, строки, логические значения и числа.
См. Также эту статью о алгоритме структурированного клонирования браузеров, который используется при отправке сообщений сотруднику и от него. Он также содержит функцию для глубокого клонирования.
В ECMAScript 6 есть метод Object.assign, который копирует значения всех перечисляемых собственных свойств из одного объекта в другой. Например:
var x = {myProp: "value"};
var y = Object.assign({}, x);
Но имейте в виду, что вложенные объекты все еще копируются как ссылки.
С jQuery вы можете копировать с расширением:
var copiedObject = jQuery.extend({}, originalObject)
последующие изменения в скопированном объекте не влияют на исходный объект, и наоборот.
Или сделать глубокую копию:
var copiedObject = jQuery.extend(true, {}, originalObject)
За MDN:
- Если вы хотите мелкую копию, используйте
Object.assign({}, a)
- Для "глубокого" копирования используйте
JSON.parse(JSON.stringify(a))
Нет необходимости во внешних библиотеках, но сначала нужно проверить совместимость браузера.
Элегантный способ клонировать объект Javascript в одну строку кода
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;
}
});
}
Существует много ответов, но ни один из них не упоминает Object.create из ECMAScript 5, который, по общему признанию, не дает точной копии, но устанавливает источник в качестве прототипа нового объекта.
Таким образом, это не точный ответ на вопрос, но это однострочное решение и, следовательно, элегантный. И это лучше всего работает в 2 случаях:
- Где такое наследство полезно (дух!)
- Где исходный объект не будет изменен, таким образом, отношения между двумя объектами не проблема.
Пример:
var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property
Почему я считаю это решение лучшим? Он родной, поэтому нет циклов, нет рекурсии. Тем не менее, старые браузеры будут нуждаться в polyfill.
Есть несколько проблем с большинством решений в Интернете. Поэтому я решил сделать продолжение, которое включает, почему принятый ответ не должен быть принят.
стартовая ситуация
Я хочу глубоко скопировать Javascript Object
со всеми своими детьми и их детьми и так далее. Но так как я не нормальный разработчик, мой Object
имеет нормальный properties
, circular structures
и даже nested objects
,
Итак, давайте создадим circular structure
и nested object
первый.
function Circ() {
this.me = this;
}
function Nested(y) {
this.y = y;
}
Давайте соберем все вместе в Object
названный a
,
var a = {
x: 'a',
circ: new Circ(),
nested: new Nested('a')
};
Далее мы хотим скопировать a
в переменную с именем b
и мутировать его.
var b = a;
b.x = 'b';
b.nested.y = 'b';
Вы знаете, что здесь произошло, потому что если бы не вы, вы бы даже не попали на этот великий вопрос.
console.log(a, b);
a --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
Теперь давайте найдем решение.
JSON
Первая попытка, которую я пытался использовать JSON
,
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
Не тратьте слишком много времени на это, вы получите TypeError: Converting circular structure to JSON
,
Рекурсивная копия (принятый "ответ")
Давайте посмотрим на принятый ответ.
function cloneSO(obj) {
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = cloneSO(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
Хорошо выглядит, а? Это рекурсивная копия объекта и обрабатывает другие типы, такие как Date
, но это не было требованием.
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
Рекурсия и circular structures
плохо работает вместе... RangeError: Maximum call stack size exceeded
нативное решение
После спора с моим коллегой, мой начальник спросил нас, что случилось, и он нашел простое решение после некоторого поиска в Google. Это называется Object.create
,
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
Это решение было добавлено в Javascript некоторое время назад и даже обрабатывает circular structure
,
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> Object {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
... и вы видите, это не сработало с вложенной структурой внутри.
полифилл для нативного раствора
Есть полифилл для Object.create
в более старом браузере, как IE 8. Это что-то вроде рекомендованного Mozilla, и, конечно, оно не идеально и приводит к той же проблеме, что и нативное решение.
function F() {};
function clonePF(o) {
F.prototype = o;
return new F();
}
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
Я положил F
выходит за рамки, чтобы мы могли взглянуть на то, что instanceof
говорит нам.
console.log(a, b);
a --> Object {
x: "a",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
b --> F {
x: "b",
circ: Circ {
me: Circ { ... }
},
nested: Nested {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> true
Та же проблема, что и у нативного решения, но немного худший результат.
лучшее (но не идеальное) решение
Копаясь, я нашел похожий вопрос ( в Javascript, когда я выполняю глубокое копирование, как избежать цикла из-за свойства "this"?) На этот, но с более лучшим решением.
function cloneDR(o) {
const gdcc = "__getDeepCircularCopy__";
if (o !== Object(o)) {
return o; // primitive value
}
var set = gdcc in o,
cache = o[gdcc],
result;
if (set && typeof cache == "function") {
return cache();
}
// else
o[gdcc] = function() { return result; }; // overwrite
if (o instanceof Array) {
result = [];
for (var i=0; i<o.length; i++) {
result[i] = cloneDR(o[i]);
}
} else {
result = {};
for (var prop in o)
if (prop != gdcc)
result[prop] = cloneDR(o[prop]);
else if (set)
result[prop] = cloneDR(cache);
}
if (set) {
o[gdcc] = cache; // reset
} else {
delete o[gdcc]; // unset again
}
return result;
}
var b = cloneDR(a);
b.x = 'b';
b.nested.y = 'b';
И давайте посмотрим на вывод...
console.log(a, b);
a --> Object {
x: "a",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "a"
}
}
b --> Object {
x: "b",
circ: Object {
me: Object { ... }
},
nested: Object {
y: "b"
}
}
console.log(typeof a, typeof b);
a --> object
b --> object
console.log(a instanceof Object, b instanceof Object);
a --> true
b --> true
console.log(a instanceof F, b instanceof F);
a --> false
b --> false
Требования совпадают, но есть еще небольшие проблемы, в том числе изменение instance
из nested
а также circ
в Object
,
Структура деревьев, которые разделяют лист, не будет скопирована, они станут двумя независимыми листами:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
заключение
Последнее решение, использующее рекурсию и кэш, может быть не лучшим, но это настоящая глубокая копия объекта. Это обрабатывает просто properties
, circular structures
а также nested object
, но это будет испортить экземпляр их при клонировании.
Если у вас все в порядке с мелкой копией, в библиотеке underscore.js есть метод clone.
y = _.clone(x);
или вы можете расширить его как
copiedObject = _.extend({},originalObject);
Хорошо, представьте, что у вас есть этот объект ниже, и вы хотите его клонировать:
let obj = {a:1, b:2, c:3}; //ES6
или же
var obj = {a:1, b:2, c:3}; //ES5
Ответ в основном зависит от того, какой ECMAscript вы используете, в ES6+
Вы можете просто использовать Object.assign
сделать клон:
let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};
или используя оператор распространения, как это:
let cloned = {...obj}; //new {a:1, b:2, c:3};
Но если вы используете ES5
, вы можете использовать несколько методов, но JSON.stringify
, просто убедитесь, что вы не используете для копирования большой кусок данных, но во многих случаях это может быть удобным способом, примерно так:
let cloned = JSON.parse(JSON.stringify(obj));
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
Обновление 06 июля 2020 г.
Есть три (3) способа клонировать объекты в JavaScript. Поскольку объекты в JavaScript являются ссылочными значениями, вы не можете просто скопировать их с помощью =.
Способы:
const food = { food: 'apple', drink: 'milk' }
// 1. Using the "Spread"
// ------------------
{ ...food }
// 2. Using "Object.assign"
// ------------------
Object.assign({}, food)
// 3. "JSON"
// ------------------
JSON.parse(JSON.stringify(food))
// RESULT:
// { food: 'apple', drink: 'milk' }
Надеюсь, что это можно будет использовать в качестве справочного материала.
Одним из особенно не элегантных решений является использование JSON-кодирования для создания глубоких копий объектов, которые не имеют методов-членов. Методология состоит в том, чтобы JSON кодировать ваш целевой объект, а затем, расшифровав его, вы получите нужную копию. Вы можете декодировать столько раз, сколько хотите, чтобы сделать столько копий, сколько вам нужно.
Конечно, функции не принадлежат JSON, поэтому это работает только для объектов без методов-членов.
Эта методология идеально подошла для моего случая использования, поскольку я храню большие двоичные объекты JSON в хранилище значений ключей, а когда они представлены в виде объектов в JavaScript API, каждый объект фактически содержит копию исходного состояния объекта, поэтому мы может вычислить дельту после того, как вызывающая сторона видоизменила экспонированный объект.
var object1 = {key:"value"};
var object2 = object1;
object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);
object2.key = "a change";
console.log(object1);// returns value
Вы можете просто использовать свойство распространения, чтобы скопировать объект без ссылок. Но будьте осторожны (см. Комментарии), "копия" находится на самом низком уровне объекта / массива. Вложенные свойства все еще являются ссылками!
Полный клон:
let x = {a: 'value1'}
let x2 = {...x}
// => mutate without references:
x2.a = 'value2'
console.log(x.a) // => 'value1'
Клон со ссылками на второй уровень:
const y = {a: {b: 'value3'}}
const y2 = {...y}
// => nested object is still a references:
y2.a.b = 'value4'
console.log(y.a.b) // => 'value4'
JavaScript на самом деле не поддерживает глубокие клоны изначально. Используйте служебную функцию. Например Рамда:
В ECMAScript 2018
let objClone = { ...obj };
Помните, что вложенные объекты по-прежнему копируются в качестве ссылки.
Из этой статьи: Как скопировать массивы и объекты в Javascript Брайана Хуисмана:
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (i == 'clone') continue;
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
} else newObj[i] = this[i]
} return newObj;
};
Для тех, кто использует AngularJS, есть также прямой метод для клонирования или расширения объектов в этой библиотеке.
var destination = angular.copy(source);
или же
angular.copy(source, destination);
Больше в документации angular.copy...
Вот функция, которую вы можете использовать.
function clone(obj) {
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = clone(obj[key]);
return temp;
}
Ответ А. Леви почти полный, вот мой маленький вклад: есть способ, как обрабатывать рекурсивные ссылки, см. Эту строку
if(this[attr]==this) copy[attr] = copy;
Если объект является элементом XML DOM, мы должны использовать вместо него cloneNode
if(this.cloneNode) return this.cloneNode(true);
Вдохновленный исчерпывающим исследованием А. Леви и подходом Келвина к созданию прототипов, я предлагаю следующее решение:
Object.prototype.clone = function() {
if(this.cloneNode) return this.cloneNode(true);
var copy = this instanceof Array ? [] : {};
for(var attr in this) {
if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
copy[attr] = this[attr];
else if(this[attr]==this) copy[attr] = copy;
else copy[attr] = this[attr].clone();
}
return copy;
}
Date.prototype.clone = function() {
var copy = new Date();
copy.setTime(this.getTime());
return copy;
}
Number.prototype.clone =
Boolean.prototype.clone =
String.prototype.clone = function() {
return this;
}
Смотрите также примечание Энди Бёрка в ответах.
Производительность
Сегодня 2020.04.30 я провожу тесты выбранных решений на Chrome v81.0, Safari v13.1 и Firefox v75.0 на MacOs High Sierra v10.13.6.
Я сосредотачиваюсь на скорости копирования ДАННЫХ (объект с полями простого типа, а не методами и т. Д.). Решения AI может создавать только поверхностную копию, решения JU могут создавать глубокую копию.
Результаты для мелкой копии
- решение
{...obj}
(A) самый быстрый в Chrome и Firefox и средний в Safari. - решение на основе
Object.assign
(B) работает быстро во всех браузерах - Решения jQuery (E) и lodash (F,G,H) средние / довольно быстрые
- решение
JSON.parse/stringify
(K) довольно медленный - решения D и U медленны во всех браузерах
Результаты для глубокой копии
- решение Q является самым быстрым во всех браузерах
- jQuery (L) и lodash (J) работают средней скорости
- решение
JSON.parse/stringify
(K) довольно медленный - решение U является самым медленным во всех браузерах
- lodash (J) и решение U вылетает в Chrome для объекта глубиной 1000 уровней
Детали
Для выбранных решений: A B C (мой) D E F G H I J K L M N O P Q R S T U, я провожу 4 теста
- shallow-small: объект с 10-ю не вложенными полями - запустить его можно ЗДЕСЬ
- мелкий-большой: объект с 1000 невложенными полями - запустить его можно ЗДЕСЬ
- deep-small: объект с 10-ю уровнями вложенных полей - запустить его можно ЗДЕСЬ
- deep-big: объект с 1000 полями вложенных уровней - вы можете запустить его ЗДЕСЬ
Объекты, используемые в тестах, показаны во фрагменте ниже.
let obj_ShallowSmall = {
field0: false,
field1: true,
field2: 1,
field3: 0,
field4: null,
field5: [],
field6: {},
field7: "text7",
field8: "text8",
}
let obj_DeepSmall = {
level0: {
level1: {
level2: {
level3: {
level4: {
level5: {
level6: {
level7: {
level8: {
level9: [[[[[[[[[['abc']]]]]]]]]],
}}}}}}}}},
};
let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{});
let obj_DeepBig = genDeepObject(1000);
// ------------------
// Show objects
// ------------------
console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));
// ------------------
// HELPERS
// ------------------
function getField(k) {
let i=k%10;
if(i==0) return false;
if(i==1) return true;
if(i==2) return k;
if(i==3) return 0;
if(i==4) return null;
if(i==5) return [];
if(i==6) return {};
if(i>=7) return "text"+k;
}
function genDeepObject(N) {
// generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}}
let obj={};
let o=obj;
let arr = [];
let a=arr;
for(let i=0; i<N; i++) {
o['level'+i]={};
o=o['level'+i];
let aa=[];
a.push(aa);
a=aa;
}
a[0]='abc';
o['end']=arr;
return obj;
}
Во фрагменте ниже представлены протестированные решения и показаны различия между ними.
function A(obj) {
return {...obj}
}
function B(obj) {
return Object.assign({}, obj);
}
function C(obj) {
return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {})
}
function D(obj) {
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
return copyOfObject;
}
function E(obj) {
return jQuery.extend({}, obj) // shallow
}
function F(obj) {
return _.clone(obj);
}
function G(obj) {
return _.clone(obj,true);
}
function H(obj) {
return _.extend({},obj);
}
function I(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
function J(obj) {
return _.cloneDeep(obj,true);
}
function K(obj) {
return JSON.parse(JSON.stringify(obj));
}
function L(obj) {
return jQuery.extend(true, {}, obj) // deep
}
function M(obj) {
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = M(obj[key]);
return temp;
}
function N(obj) {
let EClone = function(obj) {
var newObj = (obj instanceof Array) ? [] : {};
for (var i in obj) {
if (i == 'EClone') continue;
if (obj[i] && typeof obj[i] == "object") {
newObj[i] = EClone(obj[i]);
} else newObj[i] = obj[i]
} return newObj;
};
return EClone(obj);
};
function O(obj) {
if (obj == null || typeof obj != "object") return obj;
if (obj.constructor != Object && obj.constructor != Array) return obj;
if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
return new obj.constructor(obj);
let to = new obj.constructor();
for (var name in obj)
{
to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];
}
return to;
}
function P(obj) {
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;
}
return clone({},obj);
}
function Q(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] = Q(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
function R(obj) {
const gdcc = "__getDeepCircularCopy__";
if (obj !== Object(obj)) {
return obj; // primitive value
}
var set = gdcc in obj,
cache = obj[gdcc],
result;
if (set && typeof cache == "function") {
return cache();
}
// else
obj[gdcc] = function() { return result; }; // overwrite
if (obj instanceof Array) {
result = [];
for (var i=0; i<obj.length; i++) {
result[i] = R(obj[i]);
}
} else {
result = {};
for (var prop in obj)
if (prop != gdcc)
result[prop] = R(obj[prop]);
else if (set)
result[prop] = R(cache);
}
if (set) {
obj[gdcc] = cache; // reset
} else {
delete obj[gdcc]; // unset again
}
return result;
}
function S(obj) {
const cache = new WeakMap(); // Map of old - new references
function copy(object) {
if (typeof object !== 'object' ||
object === null ||
object instanceof HTMLElement
)
return object; // primitive value or HTMLElement
if (object instanceof Date)
return new Date().setTime(object.getTime());
if (object instanceof RegExp)
return new RegExp(object.source, object.flags);
if (cache.has(object))
return cache.get(object);
const result = object instanceof Array ? [] : {};
cache.set(object, result); // store reference to object before the recursive starts
if (object instanceof Array) {
for(const o of object) {
result.push(copy(o));
}
return result;
}
const keys = Object.keys(object);
for (const key of keys)
result[key] = copy(object[key]);
return result;
}
return copy(obj);
}
function T(obj){
var clonedObjectsArray = [];
var originalObjectsArray = []; //used to remove the unique ids when finished
var next_objid = 0;
function objectId(obj) {
if (obj == null) return null;
if (obj.__obj_id == undefined){
obj.__obj_id = next_objid++;
originalObjectsArray[obj.__obj_id] = obj;
}
return obj.__obj_id;
}
function cloneRecursive(obj) {
if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0; i < obj.length; ++i) {
copy[i] = cloneRecursive(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
if (clonedObjectsArray[objectId(obj)] != undefined)
return clonedObjectsArray[objectId(obj)];
var copy;
if (obj instanceof Function)//Handle Function
copy = function(){return obj.apply(this, arguments);};
else
copy = {};
clonedObjectsArray[objectId(obj)] = copy;
for (var attr in obj)
if (attr != "__obj_id" && obj.hasOwnProperty(attr))
copy[attr] = cloneRecursive(obj[attr]);
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
var cloneObj = cloneRecursive(obj);
//remove the unique ids
for (var i = 0; i < originalObjectsArray.length; i++)
{
delete originalObjectsArray[i].__obj_id;
};
return cloneObj;
}
function U(obj) {
/*
Deep copy objects by value rather than by reference,
exception: `Proxy`
*/
const seen = new WeakMap()
return clone(obj)
function defineProp(object, key, descriptor = {}, copyFrom = {}) {
const { configurable: _configurable, writable: _writable }
= Object.getOwnPropertyDescriptor(object, key)
|| { configurable: true, writable: true }
const test = _configurable // Can redefine property
&& (_writable === undefined || _writable) // Can assign to property
if (!test || arguments.length <= 2) return test
const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
|| { configurable: true, writable: true } // Custom…
|| {}; // …or left to native default settings
["get", "set", "value", "writable", "enumerable", "configurable"]
.forEach(attr =>
descriptor[attr] === undefined &&
(descriptor[attr] = basisDesc[attr])
)
const { get, set, value, writable, enumerable, configurable }
= descriptor
return Object.defineProperty(object, key, {
enumerable, configurable, ...get || set
? { get, set } // Accessor descriptor
: { value, writable } // Data descriptor
})
}
function clone(object) {
if (object !== Object(object)) return object /*
—— Check if the object belongs to a primitive data type */
if (object instanceof Node) return object.cloneNode(true) /*
—— Clone DOM trees */
let _object // The clone of object
switch (object.constructor) {
case Array:
case Object:
_object = cloneObject(object)
break
case Date:
_object = new Date(+object)
break
case Function:
const fnStr = String(object)
_object = new Function("return " +
(/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
? "function " : ""
) + fnStr
)()
copyPropDescs(_object, object)
break
case RegExp:
_object = new RegExp(object)
break
default:
switch (Object.prototype.toString.call(object.constructor)) {
// // Stem from:
case "[object Function]": // `class`
case "[object Undefined]": // `Object.create(null)`
_object = cloneObject(object)
break
default: // `Proxy`
_object = object
}
}
return _object
}
function cloneObject(object) {
if (seen.has(object)) return seen.get(object) /*
—— Handle recursive references (circular structures) */
const _object = Array.isArray(object)
? []
: Object.create(Object.getPrototypeOf(object)) /*
—— Assign [[Prototype]] for inheritance */
seen.set(object, _object) /*
—— Make `_object` the associative mirror of `object` */
Reflect.ownKeys(object).forEach(key =>
defineProp(_object, key, { value: clone(object[key]) }, object)
)
return _object
}
function copyPropDescs(target, source) {
Object.defineProperties(target,
Object.getOwnPropertyDescriptors(source)
)
}
}
// ------------------------
// Test properties
// ------------------------
console.log(` shallow deep func circ undefined date RegExp bigInt`)
log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);
console.log(` shallow deep func circ undefined date RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)
// ------------------------
// Helper functions
// ------------------------
function deepCompare(obj1,obj2) {
return JSON.stringify(obj1)===JSON.stringify(obj2);
}
function getCase() { // pure data case
return {
undef: undefined,
bool: true, num: 1, str: "txt1",
e1: null, e2: [], e3: {}, e4: 0, e5: false,
arr: [ false, 2, "txt3", null, [], {},
[ true,4,"txt5",null, [], {}, [true,6,"txt7",null,[],{} ],
{bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
],
{bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
],
obj: {
bool: true, num: 12, str: "txt13",
e1: null, e2: [], e3: {}, e4: 0, e5: false,
arr: [true,14,"txt15",null,[],{} ],
obj: {
bool: true, num: 16, str: "txt17",
e1: null, e2: [], e3: {}, e4: 0, e5: false,
arr: [true,18,"txt19",null,[],{} ],
obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
}
}
};
}
function check(org, copy, field, newValue) {
copy[field] = newValue;
return deepCompare(org,copy);
}
function testFunc(f) {
let o = { a:1, fun: (i,j)=> i+j };
let c = f(o);
let val = false
try{
val = c.fun(3,4)==7;
} catch(e) { }
return val;
}
function testCirc(f) {
function Circ() {
this.me = this;
}
var o = {
x: 'a',
circ: new Circ(),
obj_circ: null,
};
o.obj_circ = o;
let val = false;
try{
let c = f(o);
val = (o.obj_circ == o) && (o.circ == o.circ.me);
} catch(e) { }
return val;
}
function testRegExp(f) {
let o = {
re: /a[0-9]+/,
};
let val = false;
try{
let c = f(o);
val = (String(c.re) == String(/a[0-9]+/));
} catch(e) { }
return val;
}
function testDate(f) {
let o = {
date: new Date(),
};
let val = false;
try{
let c = f(o);
val = (+new Date(c.date) == +new Date(o.date));
} catch(e) { }
return val;
}
function testBigInt(f) {
let val = false;
try{
let o = {
big: 123n,
};
let c = f(o);
val = o.big == c.big;
} catch(e) { }
return val;
}
function log(f) {
let o = getCase(); // orginal object
let oB = getCase(); // "backup" used for shallow valid test
let c1 = f(o); // copy 1 for reference
let c2 = f(o); // copy 2 for test shallow values
let c3 = f(o); // copy 3 for test deep values
let is_proper_copy = deepCompare(c1,o); // shoud be true
// shallow changes
let testShallow =
[ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ]
.reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
// should be true (original object shoud not have changed shallow fields)
let is_valid = deepCompare(o,oB);
// deep test (intruduce some change)
if (c3.arr[6]) c3.arr[6][7].num = 777;
let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
let diff_deep = !deepCompare(c1,c3); // shoud be true (deep field was copied)
let can_copy_functions = testFunc(f);
let can_copy_circular = testCirc(f);
let can_copy_regexp = testRegExp(f);
let can_copy_date = testDate(f);
let can_copy_bigInt = testBigInt(f);
let has_undefined = 'undef' in c1; // field with undefined value is copied?
let is_ok = is_valid && is_proper_copy;
let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
testFunc(f);
if(is_ok) {
console.log(`${f.name} ${b(diff_shallow)} ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)} ${b(can_copy_date)} ${b(can_copy_regexp)} ${b(can_copy_bigInt)}`)
} else {
console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1})
}
}
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
This snippet only presents tested solutions and show differences between them (but it no make performence tests)
Ниже приведены примеры результатов для Chrome для мелкого и большого объекта.
В ES-6 вы можете просто использовать Object.assign(...). Пример:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
Хорошая ссылка здесь: https://googlechrome.github.io/samples/object-assign-es6/
Интересует клонирование простых объектов:
JSON.parse (JSON.stringify (json_original));
Источник: Как скопировать объект JavaScript в новую переменную НЕ по ссылке?
Structured Cloning
The HTML standard includes an internal structured cloning/serialization algorithm that can create deep clones of objects. It is still limited to certain built-in types, but in addition to the few types supported by JSON it also supports Dates, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays, and probably more in the future. It also preserves references within the cloned data, allowing it to support cyclical and recursive structures that would cause errors for JSON.
Support in Node.js: Experimental
The v8
module in Node.js currently (as of Node 11) exposes the structured serialization API directly, but this functionality is still marked as "experimental", and subject to change or removal in future versions. If you're using a compatible version, cloning an object is as simple as:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Direct Support in Browsers: Maybe Eventually?
Browsers do not currently provide a direct interface for the structured cloning algorithm, but a global structuredClone()
function has been discussed in whatwg/html#793 on GitHub. As currently proposed, using it for most purposes would be as simple as:
const clone = structuredClone(original);
Unless this is shipped, browsers' structured clone implementations are only exposed indirectly.
Asynchronous Workaround: Usable.
The lower-overhead way to create a structured clone with existing APIs is to post the data through one port of a MessageChannels. The other port will emit a message
event with a structured clone of the attached .data
. Unfortunately, listening for these events is necessarily asynchronous, and the synchronous alternatives are less practical.
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);
Example Use:
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();
Synchronous Workarounds: Awful!
There are no good options for creating structured clones synchronously. Here are a couple of impractical hacks instead.
history.pushState()
and history.replaceState()
both create a structured clone of their first argument, and assign that value to history.state
. You can use this to create a structured clone of any object like this:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Example Use:
'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();
Though synchronous, this can be extremely slow. It incurs all of the overhead associated with manipulating the browser history. Calling this method repeatedly can cause Chrome to become temporarily unresponsive.
The Notification
constructor creates a structured clone of its associated data. It also attempts to display a browser notification to the user, but this will silently fail unless you have requested notification permission. In case you have the permission for other purposes, we'll immediately close the notification we've created.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Example Use:
'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();
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
Решение ES6, если вы хотите (поверхностно) клонировать экземпляр класса, а не просто объект свойства.
Вы можете клонировать объект и удалить любую ссылку из предыдущего, используя одну строку кода. Просто сделайте:
var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}
Для браузеров / движков, которые в настоящее время не поддерживают Object.create, вы можете использовать этот polyfill:
// Polyfill Object.create if it does not exist
if (!Object.create) {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
Новый ответ на старый вопрос! Если вы имеете удовольствие от использования ECMAScript 2016 (ES6) с синтаксисом распространения, это легко.
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
Это обеспечивает чистый метод для мелкой копии объекта. Создание глубокой копии, означающей создание новой копии каждого значения в каждом рекурсивно-вложенном объекте, требует одного из более тяжелых решений, указанных выше.
JavaScript продолжает развиваться.
Я думаю, что есть простой и рабочий ответ. При глубоком копировании есть две проблемы:
- Держите свойства зависимыми друг от друга.
- И сохранить методы на клонированном объекте.
Поэтому я думаю, что одним из простых решений будет сначала сериализовать и десериализовать, а затем назначить для него функции копирования.
let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);
Хотя на этот вопрос есть много ответов, я надеюсь, что этот тоже поможет.
Для глубокого копирования я использую:
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(obj)); // { a: 5, b: { c: 5}}
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
Мелкая Копия: lodash _.clone()
Мелкая копия может быть сделана простым копированием ссылки.
let obj1 = {
a: 0,
b: {
c: 0,
e: {
f: 0
}
}
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}
console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}
Deep Copy: lodash _.cloneDeep()
поля разыменовываются: вместо ссылок на копируемые объекты
let obj1 = {
a: 0,
b: {
c: 0,
e: {
f: 0
}
}
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;
console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}
console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}
(В основном это интеграция @ A. Levy, @ Jan Turoň, ответов @ Reduce, @ LeviRoberts, комментариев @ RobG, большое им спасибо!)
Глубокая копия? - ДА!
Мелкая копия? - НЕТ!
функция
function clone(obj) { // Deep copy by value instead of reference
if (!(obj === Object(obj))) return obj // Check if the object belongs to a primitive data type
if (obj instanceof Node) return obj.cloneNode(true) // Clone DOM trees
let _obj // The clone of obj
switch (obj.constructor) {
default:
case Object:
_obj = Object.create(Object.getPrototypeOf(obj)) // Assign [[Prototype]] for inheritance
for (let key of Reflect.ownKeys(obj)) {
obj[key] === obj // Handle infinite recursive references (or circular structures)
? _obj[key] = _obj
: _obj[key] = clone(obj[key])
}
break
case Array:
_obj = obj.map(i => Array.isArray(i) ? clone(i) : i)
break
case Date:
_obj = new Date(+obj)
break
case Function:
_obj = new Function("return " + String(obj))()
Object.defineProperties(_obj, Object.getOwnPropertyDescriptors(obj))
break
}
return _obj
}
// Тесты
obj0 = {
u: undefined,
nul: null,
t: true,
n: 9,
str1: "string",
str2: "",
sym: Symbol("symbol"),
[Symbol("e")]: Math.E,
function(params) {},
o: {
n: 0,
o: {
f: function(...args) {}
}
},
arr: [ 0, [ 1, 2 ] ],
d: new Date()
}
obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a=0, b=0) {return a + b}
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60*1000)
console.log("-".repeat(2**6))
console.log(">:>: Test - Routinely")
console.log("obj0:\n ",obj0)
console.log("obj1:\n ",obj1)
console.log("obj0 has not been interfered:\n ",JSON.stringify(obj0))
console.log("obj1:\n ",JSON.stringify(obj1))
console.log("-".repeat(2**6))
console.log(">:>: Test - Infinite recursion")
obj0.o.recursion = obj0.o
obj1.o.recursion = null
console.log("obj0:\n ",obj0)
console.log("obj1:\n ",obj1)
console.log("-".repeat(2**6))
console.log(">:>: Test - Classes")
class Person {
constructor(name) {
this.name = name
}
}
class Boy extends Person {}
Boy.prototype.sex = "M"
boy0 = new Boy
boy0.hobby = {sport: "gliding"}
boy1 = clone(boy0)
boy1.hobby.sport = "piloting"
boy0.name = "neo"
boy1.name = "one"
console.log("boy0:\n ",boy0)
console.log("boy1:\n ",boy1)