Каков наиболее эффективный способ клонирования объекта JavaScript? Я видел, как obj = eval(uneval(o));
используется, но это нестандартно и поддерживается только Firefox.
Я делал такие вещи, как obj = JSON.parse(JSON.stringify(o));
, но сомневаюсь в эффективности.
Я также видел функции рекурсивного копирования с различными недостатками.
Я удивлен, что канонического решения не существует.
Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript?
Ответы (63)
Родное глубокое клонирование
Это называется структурированным клонированием, экспериментально работает в Node 11 и более поздних версиях и, надеюсь, появится в браузерах. См. это ответьте, чтобы получить более подробную информацию.
Быстрое клонирование с потерей данных - JSON.parse / stringify
Если вы не используете Date
s, functions, undefined
, Infinity
, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays или другие сложные типы в вашем объекте, очень простой лайнер для глубокого клонирования объекта:
JSON.parse(JSON.stringify(object))
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
re: /.*/, // lost
}
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
См. Корбана ответьте для тестов.
Надежное клонирование с использованием библиотеки
Поскольку клонирование объектов нетривиально (сложные типы, циклические ссылки, функции и т. Д.), Большинство основных библиотек предоставляют функции для клонирования объектов. Не изобретайте велосипед - если вы уже используете библиотеку, проверьте, есть ли в ней функция клонирования объектов. Например,
- lodash -
cloneDeep
; можно импортировать отдельно через модуль lodash.clonedeep, и это, вероятно, ваш лучший выбор, если вы: re еще не использует библиотеку, которая предоставляет функцию глубокого клонирования - AngularJS -
angular.copy
- jQuery -
jQuery.extend(true, { }, oldObject)
;.clone()
только клонирует элементы DOM - просто библиотека -
just-clone
; Часть библиотеки модулей npm с нулевой зависимостью, которые делают только одно. Утилиты без вины на все случаи жизни.
ES6 (мелкая копия)
Для полноты заметим, что ES6 предлагает два механизма поверхностного копирования: Object.assign()
и синтаксис распространения. который копирует значения всех перечислимых собственных свойств из одного объекта в другой. Например:
var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1}; // Spread Syntax
var A = { b: [ { a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] } ] }; B = Object.assign( {}, A ); delete B.b[0].b;
Он также изменит объект A!
- person Gabriel Hautclocq; 30.09.2020
Object.assign()
действительно бесполезен, если ваш объект для копирования содержит в себе массивы, особенно массивы объектов. Просто выяснил это на собственном горьком опыте ...
- person cst1992; 29.10.2020
A.b
или B.b
оба относятся к одному и тому же объекту в памяти. если A
имеет свойство с не объектным значением (например, числа или строки), оно будет скопировано обычным образом. Но когда копируется свойство, содержащее значение объекта, оно копируется по ссылке, а не по значению. Также имейте в виду, что массив - это объект в JS. доказательство: typeof [] == 'object' && [] instanceof Array
- person Unicornist; 15.12.2020
proto: true
,
- person Dmitry; 08.03.2021
{...A1}
, что лучше любого решения на основе for
оползня. У меня есть случай объекта, подобного словарю Python, где все значения являются логическими. Альтернатива мелкой {...obj}
работает как шарм.
- person Ricardo; 27.04.2021
Оцените этот тест: http://jsben.ch/#/bWfk9
В моих предыдущих тестах, где скорость была главной проблемой, я обнаружил, что
JSON.parse(JSON.stringify(obj))
чтобы быть самым медленным способом глубокого клонирования объекта (он медленнее, чем jQuery.extend с deep
установлен флаг true на 10-20%).
jQuery.extend работает довольно быстро, если для флага deep
установлено значение false
(неглубокий клон). Это хороший вариант, потому что он включает некоторую дополнительную логику для проверки типа и не копирует неопределенные свойства и т. Д., Но это также немного замедлит вас.
Если вы знаете структуру объектов, которые пытаетесь клонировать, или можете избежать глубоких вложенных массивов, вы можете написать простой цикл for (var i in obj)
для клонирования вашего объекта при проверке hasOwnProperty, и это будет намного быстрее, чем jQuery.
Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить НАМНОГО БОЛЬШЕ ПРОИЗВОДИТЕЛЬНОСТИ, просто вставив процедуру клонирования и создав объект вручную.
Механизмы трассировки JavaScript плохо справляются с оптимизацией for..in
циклов, и проверка hasOwnProperty также замедлит вас. Ручное клонирование, когда скорость абсолютно необходима.
var clonedObject = {
knownProp: obj.knownProp,
..
}
Остерегайтесь использования метода JSON.parse(JSON.stringify(obj))
для Date
объектов - JSON.stringify(new Date())
возвращает строковое представление даты в формате ISO, которое JSON.parse()
не конвертирует обратно в объект Date
. Дополнительные сведения см. в этом ответе а>.
Кроме того, обратите внимание, что, по крайней мере, в Chrome 65 встроенное клонирование не подходит. Согласно JSPerf, выполнение нативного клонирования путем создания новой функции почти 800x медленнее, чем использование JSON.stringify, которое невероятно быстро во всех направлениях.
Обновление для ES6 < / сильный>
Если вы используете Javascript ES6, попробуйте этот собственный метод клонирования или неглубокого копирования.
Object.assign({}, obj);
_.clone
и Object.assign
) с некоторым глубоким клонированием (JSON.parse(JSON.stringify())
). Во-вторых, он говорит о глубоком клоне для lodash, но вместо этого делает мелкий клон.
- person papillon; 15.02.2021
Предполагая, что у вас есть только переменные, а не какие-либо функции в вашем объекте, вы можете просто использовать:
var newObject = JSON.parse(JSON.stringify(oldObject));
Структурированное клонирование
Стандарт HTML включает внутренний структурированный алгоритм клонирования / сериализации, который может создавать глубокие клоны объектов. Он по-прежнему ограничен некоторыми встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает даты, регулярные выражения, карты, наборы, большие двоичные объекты, списки файлов, ImageDatas, разреженные массивы, типизированные массивы и, возможно, многое другое в будущем. . Он также сохраняет ссылки в клонированных данных, позволяя поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.
Поддержка в Node.js: экспериментальная ????
Модуль v8
в Node.js в настоящее время (начиная с Node 11) напрямую предоставляет API структурированной сериализации, но эта функция по-прежнему помечена как «экспериментальная» и может быть изменена или удалена в будущих версиях. Если вы используете совместимую версию, клонировать объект так же просто:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Прямая поддержка в браузерах: может быть, когда-нибудь? ????
В настоящее время браузеры не предоставляют прямой интерфейс для структурированного алгоритма клонирования, но глобальная функция structuredClone()
обсуждалась в whatwg / html # 793 на GitHub. В соответствии с нынешним предложением использовать его для большинства целей так же просто, как:
const clone = structuredClone(original);
Если это не сделано, реализации структурированных клонов браузеров доступны только косвенно.
Асинхронное решение: можно использовать. ????
Более простой способ создать структурированный клон с существующими API - это разместить данные через один порт MessageChannels. Другой порт будет генерировать событие message
со структурированным клоном присоединенного .data
. К сожалению, прослушивание этих событий обязательно асинхронно, а синхронные альтернативы менее практичны.
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
Пример использования:
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();
Синхронные обходные пути: ужасно! ????
Нет хороших вариантов для синхронного создания структурированных клонов. Вот пара непрактичных уловок.
history.pushState()
и history.replaceState()
создают структурированный клон своего первого аргумента и присваивают это значение history.state
. Вы можете использовать это для создания структурированного клона любого объекта, подобного этому:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Пример использования:
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
main();
Хотя это синхронно, это может происходить очень медленно. Он влечет за собой все накладные расходы, связанные с манипулированием историей браузера. Повторный вызов этого метода может привести к тому, что Chrome временно перестанет отвечать на запросы.
Notification
конструктор создает структурированный клон связанных с ним данных. . Он также пытается отобразить уведомление браузера для пользователя, но это не удастся, если вы не запросили разрешение на уведомление. Если у вас есть разрешение для других целей, мы немедленно закроем созданное нами уведомление.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Пример использования:
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.close();
return n.data;
};
main();
Если бы встроенного не было, можно попробовать:
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;
}
Эффективный способ клонирования (а не глубокого клонирования) объекта в одной строке кода
Метод 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;
}
});
}
Код:
// 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);
Вот что я использую:
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;
}
Object.assign({}, a)
сделал.
- person iMartin; 08.04.2021
let o = {}; o.o = o; cloneObject(o);
- person Gershy; 27.04.2021
Глубокое копирование объектов в JavaScript (я считаю лучшим и самым простым)
1. Использование JSON.parse (JSON.stringify (object));
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
2. С помощью созданного метода
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(obj[i] != null && typeof(obj[i])=="object")
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = cloneObject(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
3. Использование ссылки _.cloneDeep в Lo-Dash, lodash
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } }
4. Использование метода Object.assign ()
var obj = {
a: 1,
b: 2
}
var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }
НО НЕПРАВИЛЬНО
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.
5. С помощью Underscore.js _.clone ссылка Underscore.js
var obj = {
a: 1,
b: 2
}
var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }
НО НЕПРАВИЛЬНО
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)
Площадка для тестирования производительности JSBEN.CH 1–3 http://jsben.ch/KVQLd
defaultsDeep
lodash. Он не должен копировать, если (i === '__proto__')
, и не должен копировать, если (i === 'constuctor' && typeof obj[i] === 'function')
.
- person Frank Fajardo; 07.03.2021
Глубокое копирование по эффективности: от лучшего к худшему.
- Переназначение "=" (только строковые массивы, числовые массивы)
- Slice (только строковые массивы, числовые массивы)
- Конкатенация (только строковые массивы, числовые массивы)
- Пользовательская функция: цикл for или рекурсивное копирование
- $ .extend из jQuery
- JSON.parse (только строковые массивы, числовые массивы, массивы объектов)
- _.clone Underscore.js (только строковые массивы, только числовые массивы)
- _.CloneDeep из Lo-Dash
Глубокое копирование массива строк или чисел (один уровень - без ссылочных указателей):
Когда массив содержит числа и строки - такие функции, как .slice (), .concat (), .splice (), оператор присваивания "=" и функция клонирования Underscore.js; сделает глубокую копию элементов массива.
Где переназначение дает наиболее быстрые результаты:
var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];
И .slice () имеет лучшую производительность, чем .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3.
var arr1 = ['a', 'b', 'c']; // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0); // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat(); // Becomes arr2b = ['a', 'b', 'c'] - deep copy
Глубокое копирование массива объектов (два или более уровней - ссылочные указатели):
var arr1 = [{object:'a'}, {object:'b'}];
Напишите пользовательскую функцию (имеет более высокую производительность, чем $ .extend () или JSON.parse):
function copy(o) {
var out, v, key;
out = Array.isArray(o) ? [] : {};
for (key in o) {
v = o[key];
out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
}
return out;
}
copy(arr1);
Используйте сторонние служебные функции:
$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash
Где jQuery $ .extend имеет лучшую производительность:
hasOwnProperty
, чтобы исключить унаследованные свойства. Я использую (возможно, даже быстрее) простой цикл for Object.keys
.
- person mikiqex; 29.06.2021
hasOwnProperty
приводит к снижению производительности (включение и отключение вызова функции из стека и выполнение кода метода) для каждой клавиши.
- person tim-montague; 30.06.2021
Cloning
Объект всегда был проблемой в JS, но все это было до ES6, я перечисляю различные способы копирования объекта в JavaScript ниже, представьте, что у вас есть объект ниже, и вы хотели бы иметь его полную копию:
var obj = {a:1, b:2, c:3, d:4};
Есть несколько способов скопировать этот объект без изменения начала координат:
- 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.");
}
- ES5 +, используя JSON.parse и JSON.stringify.
var deepCopyObj = JSON.parse(JSON.stringify(obj));
- AngularJs:
var deepCopyObj = angular.copy(obj);
- jQuery:
var deepCopyObj = jQuery.extend(true, {}, obj);
- UnderscoreJs и Loadash:
var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy
Надеюсь, это поможет ...
Существует библиотека (называемая «clone»), которая с этим справляется достаточно хорошо. Он обеспечивает наиболее полное рекурсивное клонирование / копирование произвольных объектов, о которых я знаю. Он также поддерживает циклические ссылки, которые пока не рассматриваются в других ответах.
Вы также можете найти его на npm. Его можно использовать как в браузере, так и в Node.js.
Вот пример того, как его использовать:
Установите его с помощью
npm install clone
или упакуйте его с помощью Ender.
ender build clone [...]
Вы также можете загрузить исходный код вручную.
Затем вы можете использовать его в своем исходном коде.
var clone = require('clone');
var a = { foo: { bar: 'baz' } }; // inital value of a
var b = clone(a); // clone a -> b
a.foo.bar = 'foo'; // change a
console.log(a); // { foo: { bar: 'foo' } }
console.log(b); // { foo: { bar: 'baz' } }
(Отказ от ответственности: я являюсь автором библиотеки.)
Я знаю, что это старый пост, но я подумал, что он может немного помочь следующему человеку, который спотыкается.
Пока вы не назначаете объект чему-либо, он не сохраняет ссылки в памяти. Итак, чтобы создать объект, которым вы хотите поделиться с другими объектами, вам нужно создать такую фабрику:
var a = function(){
return {
father:'zacharias'
};
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Если вы его используете, в библиотеке Underscore.js есть clone.
var newObject = _.clone(oldObject);
Вот версия ответа ConroyP выше, которая работает, даже если у конструктора есть требуемые параметры:
//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
object_create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}
function deepCopy(obj) {
if(obj == null || typeof(obj) !== 'object'){
return obj;
}
//make sure the returned object has the same prototype as the original
var ret = object_create(obj.constructor.prototype);
for(var key in obj){
ret[key] = deepCopy(obj[key]);
}
return ret;
}
Эта функция также доступна в моей библиотеке simpleoo.
Изменить:
Вот более надежная версия (благодаря Джастину МакКэндлессу теперь также поддерживаются циклические ссылки):
/**
* Deep copy an object (make copies of all its object properties, sub-properties, etc.)
* An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
* that doesn't break if the constructor has required parameters
*
* It also borrows some code from http://stackoverflow.com/a/11621004/560114
*/
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
if(src === null || typeof(src) !== 'object'){
return src;
}
//Honor native/custom clone methods
if(typeof src.clone == 'function'){
return src.clone(true);
}
//Special cases:
//Date
if(src instanceof Date){
return new Date(src.getTime());
}
//RegExp
if(src instanceof RegExp){
return new RegExp(src);
}
//DOM Element
if(src.nodeType && typeof src.cloneNode == 'function'){
return src.cloneNode(true);
}
// Initialize the visited objects arrays if needed.
// This is used to detect cyclic references.
if (_visited === undefined){
_visited = [];
_copiesVisited = [];
}
// Check if this object has already been visited
var i, len = _visited.length;
for (i = 0; i < len; i++) {
// If so, get the copy we already made
if (src === _visited[i]) {
return _copiesVisited[i];
}
}
//Array
if (Object.prototype.toString.call(src) == '[object Array]') {
//[].slice() by itself would soft clone
var ret = src.slice();
//add it to the visited array
_visited.push(src);
_copiesVisited.push(ret);
var i = ret.length;
while (i--) {
ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
}
return ret;
}
//If we've reached here, we have a regular object
//make sure the returned object has the same prototype as the original
var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
if (!proto) {
proto = src.constructor.prototype; //this line would probably only be reached by very old browsers
}
var dest = object_create(proto);
//add this object to the visited array
_visited.push(src);
_copiesVisited.push(dest);
for (var key in src) {
//Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
//For an example of how this could be modified to do so, see the singleMixin() function
dest[key] = deepCopy(src[key], _visited, _copiesVisited);
}
return dest;
}
//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
object_create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}
Следующее создает два экземпляра одного и того же объекта. Я нашел и использую сейчас. Это просто и удобно.
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Крокфорд предлагает (и я предпочитаю) использовать эту функцию:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var newObject = object(oldObject);
Он лаконичен, работает так, как ожидалось, и вам не нужна библиотека.
РЕДАКТИРОВАТЬ:
Это полифилл для Object.create
, так что вы тоже можете его использовать.
var newObject = Object.create(oldObject);
ПРИМЕЧАНИЕ. Если вы используете что-то из этого, у вас могут возникнуть проблемы с некоторыми итерациями, использующими hasOwnProperty
. Потому что create
создает новый пустой объект, который наследует oldObject
. Но он по-прежнему полезен и практичен для клонирования объектов.
Например, если oldObject.a = 5;
newObject.a; // is 5
но:
oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
В Lodash есть отличный метод _.cloneDeep (value):
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
Неглубокая копия однострочника (ECMAScript 5th edition):
var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});
console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true
И мелкая однострочная копия (6-е издание ECMAScript, 2015 г.):
var origin = { foo : {} };
var copy = Object.assign({}, origin);
console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true
Кажется, что еще не существует идеального оператора глубокого клонирования для объектов, подобных массиву. Как показано в приведенном ниже коде, jQuery cloner Джона Ресига превращает массивы с нечисловыми свойствами в объекты, которые не являются массивами, а JSON cloner RegDwight удаляет нечисловые свойства. Следующие тесты иллюстрируют эти моменты в нескольких браузерах:
function jQueryClone(obj) {
return jQuery.extend(true, {}, obj)
}
function JSONClone(obj) {
return JSON.parse(JSON.stringify(obj))
}
var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);
alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
"\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
"\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
"\nAnd what are the JSONClone names? " + JSONCopy.names)
Просто потому, что я не видел упоминания AngularJS и подумал, что люди могут захотеть узнать ...
angular.copy
также предоставляет метод глубокого копирования объектов и массивов.
angular.extend({},obj);
- person Galvani; 21.09.2016
jQuery.extend
и angular.extend
- мелкие копии. angular.copy
- это глубокая копия.
- person Dan Atkinson; 15.10.2016
У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект JavaScript» или нет.
Предположим также, что вы намерены создать полный клон без ссылок прототипа на исходный объект. Если вас не интересует полный клон, вы можете использовать многие из подпрограмм Object.clone (), представленных в некоторых других ответах (шаблон Крокфорда).
Для простых старых объектов JavaScript проверенный и действительно хороший способ клонировать объект в современных средах выполнения довольно просто:
var clone = JSON.parse(JSON.stringify(obj));
Обратите внимание, что исходный объект должен быть чистым объектом JSON. Это означает, что все его вложенные свойства должны быть скалярами (такими как логическое значение, строка, массив, объект и т. Д.). Никакие функции или специальные объекты, такие как RegExp или Date, не будут клонированы.
Это эффективно? Черт возьми, да. Мы испробовали всевозможные методы клонирования, и это работает лучше всего. Я уверен, что какой-нибудь ниндзя мог бы придумать более быстрый метод. Но я подозреваю, что мы говорим о незначительных выгодах.
Такой подход просто и легко реализовать. Превратите это в удобную функцию, и если вам действительно нужно выжать немного прироста, перейдите на более позднее время.
Теперь, для непростых объектов JavaScript, на самом деле нет простого ответа. Фактически, этого не может быть из-за динамической природы функций JavaScript и внутреннего состояния объекта. Глубокое клонирование структуры JSON с функциями внутри требует воссоздания этих функций и их внутреннего контекста. А в JavaScript просто нет стандартизированного способа сделать это.
И снова правильный способ сделать это - использовать удобный метод, который вы объявляете и повторно используете в своем коде. Удобный метод может быть наделен некоторым пониманием ваших собственных объектов, чтобы вы могли убедиться, что правильно воссоздали граф в новом объекте.
Мы написали свои собственные, но здесь описан лучший общий подход, который я видел:
http://davidwalsh.name/javascript-clone
Это правильная идея. Автор (Дэвид Уолш) прокомментировал клонирование обобщенных функций. Это то, что вы можете сделать, в зависимости от вашего варианта использования.
Основная идея заключается в том, что вам нужно специально обрабатывать создание экземпляров ваших функций (или, так сказать, прототипных классов) для каждого типа. Здесь он привел несколько примеров для RegExp и Date.
Этот код не только краток, но и очень удобен для чтения. Довольно легко расширить.
Это эффективно? Черт возьми, да. Учитывая, что цель состоит в том, чтобы создать настоящий клон с глубокой копией, вам придется пройтись по членам графа исходного объекта. При таком подходе вы можете точно настроить, какие дочерние элементы обрабатывать и как вручную обрабатывать настраиваемые типы.
Итак, поехали. Два подхода. На мой взгляд, оба они эффективны.
Обычно это не самое эффективное решение, но оно делает то, что мне нужно. Ниже приведены простые тестовые примеры ...
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
Для людей, которые хотят использовать версию 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)
Я опаздываю с ответом на этот вопрос, но у меня есть другой способ клонирования объекта:
function cloneObject(obj) {
if (obj === null || typeof(obj) !== 'object')
return obj;
var temp = obj.constructor(); // changed
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = cloneObject(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
var b = cloneObject({"a":1,"b":2}); // calling
что намного лучше и быстрее:
var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));
а также
var a = {"a":1,"b":2};
// Deep copy
var newObject = jQuery.extend(true, {}, a);
Я проверил код, и вы можете проверить результаты здесь:
и делимся результатами: Ссылки: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
obj['isActiveClone'] = null
, а затем удаляете его? А почему бы тебе не позвонить obj.hasOwnProperty(key)
?
- person Aykut Kllic; 16.04.2021
Только когда вы можете использовать ECMAScript 6 или транспилеры.
Функции:
- Не запускает геттер / сеттер при копировании.
- Сохраняет геттер / сеттер.
- Сохраняет информацию о прототипе.
- Работает как с объектным литералом, так и с функциональным объектно-ориентированным методом. письменные стили.
Код:
function clone(target, source){
for(let key in source){
// Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
let descriptor = Object.getOwnPropertyDescriptor(source, key);
if(descriptor.value instanceof String){
target[key] = new String(descriptor.value);
}
else if(descriptor.value instanceof Array){
target[key] = clone([], descriptor.value);
}
else if(descriptor.value instanceof Object){
let prototype = Reflect.getPrototypeOf(descriptor.value);
let cloneObject = clone({}, descriptor.value);
Reflect.setPrototypeOf(cloneObject, prototype);
target[key] = cloneObject;
}
else {
Object.defineProperty(target, key, descriptor);
}
}
let prototype = Reflect.getPrototypeOf(source);
Reflect.setPrototypeOf(target, prototype);
return target;
}
Я не согласен с ответом, набравшим наибольшее количество голосов, здесь. Рекурсивное глубокое клонирование намного быстрее, чем упомянутый подход JSON.parse (JSON.stringify (obj)).
- Jsperf занимает здесь первое место: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- Jsben из приведенного выше ответа обновлен, чтобы показать, что рекурсивный глубокий клон превосходит все остальные упомянутые: http://jsben.ch/13YKQ
А вот функция для быстрого ознакомления:
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
}
if(o instanceof Date) return new Date(o.valueOf());
после проверки на null `
- person Luis; 22.08.2017
Вот комплексный метод 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;
};
AngularJS
Что ж, если вы используете angular, вы тоже можете это сделать
var newObject = angular.copy(oldObject);
Я использую библиотеку клонов npm. Видимо в браузере тоже работает.
https://www.npmjs.com/package/clone
let a = clone(b)
В 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;
}
prop
, если (prop === 'constuctor' && typeof src[prop] === 'function')
или если (prop === '__proto__')
- person Frank Fajardo; 07.03.2021
Однострочное решение ECMAScript 6 (особые типы объектов, такие как Date / Regex, не обрабатываются):
const clone = (o) =>
typeof o === 'object' && o !== null ? // only clone objects
(Array.isArray(o) ? // if cloning an array
o.map(e => clone(e)) : // clone each of its elements
Object.keys(o).reduce( // otherwise reduce every key in the object
(r, k) => (r[k] = clone(o[k]), r), {} // and save its cloned value into a new object
)
) :
o; // return non-objects as is
var x = {
nested: {
name: 'test'
}
};
var y = clone(x);
console.log(x.nested !== y.nested);
В Lodash есть функция, которая вам это нравится.
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}
Прочтите документы здесь.
//Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
- person RobbyD; 20.07.2017
Пример ES 2017:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
{ foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3}
и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}
. Разве это не то, чего вы ожидаете?
- person codeMonkey; 10.06.2018
fooBaz: 44
для testObj2
и testObj3
в консоли ... (снимок экрана)
- person Takeshi Tokugawa YD; 10.06.2018
Ответов много, но ни один из них не дал желаемого эффекта. Я хотел использовать возможности глубокой копии 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;
}
}
}
Обычно я использую var newObj = JSON.parse( JSON.stringify(oldObje) );
, но вот более правильный способ:
var o = {};
var oo = Object.create(o);
(o === oo); // => false
Смотрите устаревшие браузеры!
Object.create
не обязательно создавать копию объекта, вместо этого он использует более старый объект в качестве прототипа для клона.
- person 16kb; 22.06.2018
Это самый быстрый из созданных мной методов, который не использует прототип, поэтому он будет поддерживать hasOwnProperty в новом объекте.
Решение состоит в том, чтобы перебрать свойства верхнего уровня исходного объекта, сделать две копии, удалить каждое свойство из оригинала, а затем сбросить исходный объект и вернуть новую копию. Он должен повторять столько раз, сколько свойств верхнего уровня. Это сохраняет все if
условия, чтобы проверить, является ли каждое свойство функцией, объектом, строкой и т. Д., И не нужно повторять каждое свойство-потомок.
Единственный недостаток заключается в том, что исходный объект должен быть снабжен своим исходным созданным пространством имен, чтобы его сбросить.
copyDeleteAndReset:function(namespace,strObjName){
var obj = namespace[strObjName],
objNew = {},objOrig = {};
for(i in obj){
if(obj.hasOwnProperty(i)){
objNew[i] = objOrig[i] = obj[i];
delete obj[i];
}
}
namespace[strObjName] = objOrig;
return objNew;
}
var namespace = {};
namespace.objOrig = {
'0':{
innerObj:{a:0,b:1,c:2}
}
}
var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';
console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
Для дальнейшего использования в текущем проекте 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).
obj2 = Object.assign({}, obj1)
. Ваш текущий код эквивалентен obj2 = obj1
.
- person Oriol; 25.01.2015
const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456;
сейчас o1.a.deep === 456
тоже.
- person Josh from Qaribou; 06.02.2017
Object.assign()
не предназначен для клонирования вложенных объектов.
- person Redu; 05.04.2017
Warning for Deep Clone - For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
- person basickarl; 21.04.2017
Есть так много способов добиться этого, но если вы хотите сделать это без какой-либо библиотеки, вы можете использовать следующее:
const cloneObject = (oldObject) => {
let newObject = oldObject;
if (oldObject && typeof oldObject === 'object') {
if(Array.isArray(oldObject)) {
newObject = [];
} else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
newObject = new Date(oldObject.getTime());
} else {
newObject = {};
for (let i in oldObject) {
newObject[i] = cloneObject(oldObject[i]);
}
}
}
return newObject;
}
Дайте мне знать, что вы думаете.
По моему опыту, рекурсивная версия значительно превосходит 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...
.
Object
(неглубокое копирование невозможно), функция рекурсивно вызывает себя со значением в качестве аргумента.
- person Parabolord; 15.08.2018
Это моя версия клонирования объектов. Это автономная версия метода 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;
}
|| typeof target[name] !== "undefined"
при тестировании if (target === to_add[name]) { continue; }
, чтобы не перезаписывать существующие элементы target
. Например, var a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a);
мы ожидаем найти b => {hello:"you", foo:"bar"}
, но с вашим кодом мы находим: b => {hello:"world", foo:"bar"}
- person AymKdn; 26.05.2017
Поскольку рекурсия слишком дорога для 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
Вот мой способ глубокого клонирования объекта со значением по умолчанию ES2015
и оператором распространения
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
const testObj = {
"type": "object",
"properties": {
"userId": {
"type": "string",
"chance": "guid"
},
"emailAddr": {
"type": "string",
"chance": {
"email": {
"domain": "fake.com"
}
},
"pattern": "[email protected]"
}
},
"required": [
"userId",
"emailAddr"
]
}
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
console.log(makeDeepCopy(testObj))
test
слишком наивен. в нем должны быть undefined
, функции, даты и null
, а не просто набор строк
- person vsync; 24.10.2020
Надеюсь это поможет.
function deepClone(obj) {
/*
* Duplicates an object
*/
var ret = null;
if (obj !== Object(obj)) { // primitive types
return obj;
}
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
ret = obj; // for ex: obj = new String("Spidergap")
} else if (obj instanceof Date) { // date
ret = new obj.constructor();
} else
ret = Object.create(obj.constructor.prototype);
var prop = null;
var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also
var props = {};
for (var i in allProps) {
prop = allProps[i];
props[prop] = false;
}
for (i in obj) {
props[i] = i;
}
//now props contain both enums and non enums
var propDescriptor = null;
var newPropVal = null; // value of the property in new object
for (i in props) {
prop = obj[i];
propDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (Array.isArray(prop)) { //not backward compatible
prop = prop.slice(); // to copy the array
} else
if (prop instanceof Date == true) {
prop = new prop.constructor();
} else
if (prop instanceof Object == true) {
if (prop instanceof Function == true) { // function
if (!Function.prototype.clone) {
Function.prototype.clone = function() {
var that = this;
var temp = function tmp() {
return that.apply(this, arguments);
};
for (var ky in this) {
temp[ky] = this[ky];
}
return temp;
}
}
prop = prop.clone();
} else // normal object
{
prop = deepClone(prop);
}
}
newPropVal = {
value: prop
};
if (propDescriptor) {
/*
* If property descriptors are there, they must be copied
*/
newPropVal.enumerable = propDescriptor.enumerable;
newPropVal.writable = propDescriptor.writable;
}
if (!ret.hasOwnProperty(i)) // when String or other predefined objects
Object.defineProperty(ret, i, newPropVal); // non enumerable
}
return ret;
}
https://github.com/jinujd/Javascript-Deep-Clone
Мой сценарий был немного другим. У меня был объект с вложенными объектами, а также с функциями. Следовательно, 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);
Что касается проблем, связанных с эффективностью, я считаю, что это самое простое и эффективное решение возникшей у меня проблемы. Буду признателен за любые комментарии по этому алгоритму, которые могли бы сделать его более эффективным.
Я думаю, что это лучшее решение, если вы хотите обобщить свой алгоритм клонирования объекта.
Его можно использовать как с 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);
}
Используйте 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;
}
Object.create(Object.getPrototypeOf(source))
вместо наследования свойств, которые вы также перезаписываете?
- person Jeremy; 07.04.2016
getPrototypeOf
на Array
превращает его индексы в ключи нового Object
.
- person Steven Vachon; 11.04.2016
Клонирование объекта с использованием современного JavaScript: ECMAScript 2015 (ранее известный как ECMAScript 6)
var original = {a: 1};
// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);
// Method 2: New object with spread operator assignment.
var copy2 = {...original};
Старые браузеры могут не поддерживать ECMAScript 2015. Распространенным решением является использование компилятора JavaScript-to-JavaScript, такого как Babel, для вывода ECMAScript 5 вашего кода JavaScript.
Как на что указал @ jim-hall, это лишь поверхностная копия. Свойства свойств копируются как ссылка: изменение одного из них приведет к изменению значения в другом объекте / экземпляре.
Для справки в будущем можно использовать этот код
ES6:
_clone: function(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
ES5:
function clone(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
E.g
var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
Не касаясь прототипического наследования, вы можете углубить одинокие объекты и массивы следующим образом;
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);
А как насчет асинхронного клонирования объекта, выполняемого Promise
?
async function clone(thingy /**/)
{
if(thingy instanceof Promise)
{
throw Error("This function cannot clone Promises.");
}
return thingy;
}
Promise.resolve(value)
разрешает клонированный value
? Я сомневаюсь в этом, мимо меня.
- person Константин Ван; 23.08.2020
Просматривая этот длинный список ответов, были рассмотрены почти все решения, кроме одного, о котором я знаю. Это список способов VANILLA JS для глубокого клонирования объекта.
JSON.parse (JSON.stringify (obj));
Через history.state с помощью pushState или replaceState
API веб-уведомлений, но у этого есть обратная сторона - запрашивать разрешения у пользователя.
Выполнение собственного рекурсивного цикла по объекту для копирования каждого уровня.
Ответа я не видел -> Использование ServiceWorkers. Сообщения (объекты), передаваемые между страницей и сценарием ServiceWorker, будут глубокими клонами любого объекта.
Для мелкой копии в стандарте ECMAScript2018 есть отличный простой метод. Это предполагает использование оператора распространения:
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
Я тестировал его в браузере Chrome, оба объекта хранятся в разных местах, поэтому изменение непосредственных дочерних значений в одном из них не изменит другого. Хотя (в примере) изменение значения в e
повлияет на обе копии.
Этот метод очень прост и понятен. Я считаю, что это лучшая практика для ответа на этот вопрос раз и навсегда.
Spread in object literals
- person mickro; 27.06.2018
obj.e
и objClone.e
; вы обнаружите, что они хранятся в одном месте.
- person Lupus Ossorum; 30.06.2018
Object.assign({},sourceObj)
клонирует объект только в том случае, если их свойство не имеет ключа ссылочного типа. бывший
obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);
clonedObj.b.push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip
Так что глубокого клонирования добиться таким способом невозможно.
Лучшее решение, которое работает, - это
var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
Это мое решение без использования какой-либо библиотеки или встроенной функции 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;
}
}
const o = {}; o.a = o; deepClone(o);
- ›ошибка рекурсии.
- person Ian; 21.08.2019
Требуются новые браузеры, но ...
Давайте расширим собственный объект и получим настоящий .extend()
;
Object.defineProperty(Object.prototype, 'extend', {
enumerable: false,
value: function(){
var that = this;
Array.prototype.slice.call(arguments).map(function(source){
var props = Object.getOwnPropertyNames(source),
i = 0, l = props.length,
prop;
for(; i < l; ++i){
prop = props[i];
if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
that[prop] = that[prop].extend(source[prop]);
}else{
Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
}
}
});
return this;
}
});
Просто вставьте это перед любым кодом, который использует .extend () для объекта.
Пример:
var obj1 = {
node1: '1',
node2: '2',
node3: 3
};
var obj2 = {
node1: '4',
node2: 5,
node3: '6'
};
var obj3 = ({}).extend(obj1, obj2);
console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
Это решение с рекурсией:
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))
[1,2]
- person vsync; 24.10.2020
Если ваш объект вложен и содержит объект данных, другой структурированный объект или какой-либо объект свойства и т. Д., То использование JSON.parse(JSON.stringify(object))
, Object.assign({}, obj)
или $.extend(true, {}, obj)
не будет работать. В этом случае используйте lodash. Это просто и легко ..
var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);
Теперь A будет вашим новым клоном obj без каких-либо ссылок.
если вы обнаружите, что делаете такие вещи регулярно (например, создаете функцию отмены повторного выполнения), возможно, стоит изучить 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
С предложением нового метода 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;}
Поскольку этому вопросу уделяется много внимания, и на него есть ответы в отношении встроенных функций, таких как Object.assign или пользовательского кода для глубокого клонирования, я хотел бы поделиться некоторыми библиотеками для глубокого клонирования,
1. esclone
npm install --savedev esclone https://www.npmjs.com/package/esclone
Пример использования в ES6:
import esclone from "esclone";
const rockysGrandFather = {
name: "Rockys grand father",
father: "Don't know :("
};
const rockysFather = {
name: "Rockys Father",
father: rockysGrandFather
};
const rocky = {
name: "Rocky",
father: rockysFather
};
const rockyClone = esclone(rocky);
Пример использования в ES5:
var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)
2. глубокая копия
npm установить глубокую копию https://www.npmjs.com/package/deep-copy а>
Пример:
var dcopy = require('deep-copy')
// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})
// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])
3. глубокий клон
$ npm install - сохранить глубокое клонирование https://www.npmjs.com/package/clone-deep
Пример:
var cloneDeep = require('clone-deep');
var obj = {a: 'b'};
var arr = [obj];
var copy = cloneDeep(arr);
obj.c = 'd';
console.log(copy);
//=> [{a: 'b'}]
console.log(arr);
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?
Как насчет объединения ключей объекта с его значениями?
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
является клоном, не ссылкой.
eval()
- вообще плохая идея, потому что многие оптимизаторы механизма Javascript должны отключаться при работе с переменными, которые устанавливаются с помощью _2 _ а>. Просто наличиеeval()
в вашем коде может привести к снижению производительности. - person user56reinstatemonica8   schedule 08.09.2014JSON
потеряет все типы Javascript, которые не имеют эквивалента в JSON. Например:JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))
будет генерировать{a: null, b: null, c: null, g: false}
- person oriadam   schedule 24.05.2017