Как вы клонируете массив объектов в Javascript?
... где каждый объект также имеет ссылки на другие объекты внутри одного массива?
Когда я впервые столкнулся с этой проблемой, я просто что-то вроде
var clonedNodesArray = nodesArray.clone()
будет существовать и искать информацию о том, как клонировать объекты в javascript. Я нашел question в StackOverflow (ответил тот же @JohnResig), и он указал, что с помощью jQuery вы можете сделать
var clonedNodesArray = jQuery.extend({}, nodesArray);
чтобы клонировать объект. Я попробовал это, но это копирует ссылки на объекты в массиве. Поэтому, если I
nodesArray[0].value = "red"
clonedNodesArray[0].value = "green"
значение обоих узловArray [0] и clonedNodesArray [0] окажется "зеленым". Затем я попробовал
var clonedNodesArray = jQuery.extend(true, {}, nodesArray);
который глубоко копирует объект, но я получил сообщения "слишком много рекурсии" и "переполнение стека" из Firebug и Opera Dragonfly соответственно.
Как бы вы это сделали? Разве это не должно быть сделано? Есть ли способ повторного использования этого в Javascript?
Ответы
Ответ 1
Проблема с вашей мелкой копией в том, что не все объекты клонированы. Хотя ссылки на каждый объект уникальны в каждом массиве, после того, как вы в конечном итоге ухватитесь за него, вы имеете дело с тем же объектом, что и раньше. Нет ничего плохого в том, как вы его клонировали... тот же результат будет получен при использовании Array.slice().
Причина, по которой у вашей глубокой копии возникают проблемы, заключается в том, что вы заканчиваете круговыми ссылками на объекты. Глубокий будет идти как можно глубже, и если у вас есть круг, он будет идти бесконечно, пока браузер не потеряет сознание.
Если структура данных не может быть представлена в виде ориентированного ациклического графа, я не уверен, что вы сможете найти универсальный метод для глубокого клонирования. Циклические графы предоставляют множество сложных угловых случаев, и, поскольку это не обычная операция, я сомневаюсь, что кто-либо написал полное решение (если это вообще возможно - это может быть не так! Но у меня нет времени, чтобы попытаться написать строгое доказательство сейчас.). Я нашел несколько хороших комментариев по этому вопросу на этой странице.
Если вам нужна глубокая копия массива объектов с циклическими ссылками, я полагаю, что вам придется написать собственный метод для обработки вашей специализированной структуры данных, такой, что это многопроходный клон:
- В первом раунде создайте клон всех объектов, которые не ссылаются на другие объекты в массиве. Следите за происхождением каждого объекта.
- Во втором раунде свяжите объекты вместе.
Ответ 2
Пока ваши объекты содержат JSON-сериализуемый контент (без функций, без Number.POSITIVE_INFINITY
и т.д.), Не требуется никаких циклов для клонирования массивов или объектов. Здесь чисто ванильное однострочное решение.
var clonedArray = JSON.parse(JSON.stringify(nodesArray))
Подводя итог комментариям ниже, основное преимущество этого подхода состоит в том, что он также клонирует содержимое массива, а не только сам массив. Основные недостатки являются его пределом работает только на JSON-сериализации содержания, и производительность (что значительно хуже, чем slice
подход, основанный).
Ответ 3
Я решил клонирование массива объектов с Object.assign
const newArray = myArray.map(a => Object.assign({}, a));
или даже короче с синтаксисом распространения
const newArray = myArray.map(a => ({...a}));
Ответ 4
Если вам нужна только небольшая копия, очень простой способ:
new_array = old_array.slice(0);
Ответ 5
Лучший и самый современный способ сделать этот клон выглядит следующим образом:
Использование оператора распространения ES... ".
Здесь самый простой пример:
var clonedObjArray = [...oldObjArray];
Таким образом, мы распределяем массив на отдельные значения и помещаем его в новый массив с помощью оператора [].
Здесь приведен более длинный пример, показывающий различные способы его работы:
let objArray = [ {a:1} , {b:2} ];
let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array
console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );
objArray[0] = {c:3};
console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]
Ответ 6
Просто клонировать любой тип массива с помощью
[].concat(data);
или, поскольку concat может не работать в некоторых браузерах IE, вы можете использовать это:
data.slice(0);
Ответ 7
Это работает для меня:
var clonedArray = $.map(originalArray, function (obj) {
return $.extend({}, obj);
});
И если вам нужна глубокая копия объектов в массиве:
var clonedArray = $.map(originalArray, function (obj) {
return $.extend(true, {}, obj);
});
Ответ 8
$.evalJSON($.toJSON(origArray));
Ответ 9
У меня может быть простой способ сделать это, не делая мучительной рекурсии и не зная всех более тонких деталей рассматриваемого объекта. Используя jQuery, просто преобразуйте свой объект в JSON с помощью jQuery $.toJSON(myObjectArray)
, затем возьмите строку JSON и оцените ее обратно на объект. BAM! Сделано и сделано! Задача решена.:)
var oldObjArray = [{ Something: 'blah', Cool: true }];
var newObjArray = eval($.toJSON(oldObjArray));
Ответ 10
Я отвечаю на этот вопрос, потому что, похоже, не существует простого и явного решения проблемы "клонирования массива объектов в Javascript":
function deepCopy (arr) {
var out = [];
for (var i = 0, len = arr.length; i < len; i++) {
var item = arr[i];
var obj = {};
for (var k in item) {
obj[k] = item[k];
}
out.push(obj);
}
return out;
}
// test case
var original = [
{'a' : 1},
{'b' : 2}
];
var copy = deepCopy(original);
// change value in copy
copy[0]['a'] = 'not 1';
// original[0]['a'] still equals 1
Это решение выполняет итерацию значений массива, затем выполняет итерации ключей объектов, сохраняя последний для нового объекта, а затем перетаскивая этот новый объект в новый массив.
См. jsfiddle. Примечание. Простой .slice()
или [].concat()
недостаточно для объектов внутри массива.
Ответ 11
Map создаст новый массив из старого (без ссылки на старый), и внутри карты вы создадите новый объект, перебираете свойства (ключи) и назначаете значения из старого объекта Array для соответствующих свойств новому объекту.
Это создаст точно такой же массив объектов.
let newArray = oldArray.map(a => {
let newObject = {};
Object.keys(a).forEach(propertyKey => {
newObject[propertyKey] = a[propertyKey];
});
return newObject ;
});
Ответ 12
JQuery extend работает нормально, просто нужно указать, что вы клонируете массив, а не объект (обратите внимание на [] вместо {} в качестве параметра на метод расширения):
var clonedNodesArray = jQuery.extend([], nodesArray);
Ответ 13
Как упоминал Дэниел Лью, циклические графики имеют некоторые проблемы. Если бы у меня была эта проблема, я бы либо добавил специальные методы clone()
к проблемным объектам, либо вспомнил, какие объекты я уже скопировал.
Я бы сделал это с переменной copyCount
, которая увеличивается на 1 каждый раз, когда вы копируете код. Скопирован объект с более низким copyCount
, чем текущий процесс копирования. Если нет, к копии, которая уже существует, следует ссылаться. Это заставляет ссылаться от оригинала на его копию.
Есть еще одна проблема: память. Если у вас есть эта ссылка от одного объекта к другому, вероятно, браузер не сможет освободить эти объекты, так как на них всегда ссылаются откуда-то. Вам нужно будет сделать второй проход, где вы установите все копии-ссылки на Null. (Если вы это сделаете, вам не нужно иметь copyCount
, но логического isCopied
будет достаточно, так как вы можете reset значение во втором проходе.)
Ответ 14
Для этой цели у lodash есть функция cloneDeep
:
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
Ответ 15
Array.slice можно использовать для копирования массива или части массива.
http://www.devguru.com/Technologies/Ecmascript/Quickref/Slice.html
Это будет работать со строками и числами.. - изменение строки в одном массиве не повлияет на другую, но объекты по-прежнему просто копируются по ссылке, поэтому изменения для ссылочных объектов в одном массиве будут иметь влияние на другой массив.
Вот пример менеджера JavaScript-отмены, который может быть полезен для этого: http://www.ridgway.co.za/archive/2007/11/07/simple-javascript-undo-manager-for-dtos.aspx
Ответ 16
Мой подход:
var temp = { arr : originalArray };
var obj = $.extend(true, {}, temp);
return obj.arr;
дает мне хороший, чистый, глубокий клон исходного массива - ни один из объектов, на которые ссылаются оригинал: -)
Ответ 17
забыть eval() (является самой неправильной функцией JS и делает код медленным) и срезом (0) (работает только для простых типов данных)
Это лучшее решение для меня:
Object.prototype.clone = function() {
var myObj = (this instanceof Array) ? [] : {};
for (i in this) {
if (i != 'clone') {
if (this[i] && typeof this[i] == "object") {
myObj[i] = this[i].clone();
} else
myObj[i] = this[i];
}
}
return myObj;
};
Ответ 18
Я был очень расстроен этой проблемой. По-видимому, проблема возникает, когда вы отправляете общий массив в метод $.extend. Итак, чтобы исправить это, я добавил небольшую проверку, и она отлично работает с универсальными массивами, массивами jQuery и любыми объектами.
jQuery.extend({
deepclone: function(objThing) {
// return jQuery.extend(true, {}, objThing);
/// Fix for arrays, without this, arrays passed in are returned as OBJECTS! WTF?!?!
if ( jQuery.isArray(objThing) ) {
return jQuery.makeArray( jQuery.deepclone($(objThing)) );
}
return jQuery.extend(true, {}, objThing);
},
});
Вызвать с помощью:
var arrNewArrayClone = jQuery.deepclone(arrOriginalArray);
// Or more simply/commonly
var arrNewArrayClone = $.deepclone(arrOriginalArray);
Ответ 19
Этот метод очень прост, и вы можете изменить свой клон без изменения исходного массива.
// Original Array
let array = [{name: 'Rafael'}, {name: 'Matheus'}];
// Cloning Array
let clone = array.map(a => {return {...a}})
// Editing the cloned array
clone[1].name = 'Carlos';
console.log('array', array)
// [{name: 'Rafael'}, {name: 'Matheus'}]
console.log('clone', clone)
// [{name: 'Rafael'}, {name: 'Carlos'}]
Ответ 20
Я использую новый метод ECMAScript 6 Object.assign:
let oldObject = [1,3,5,"test"];
let newObject = Object.assign({}, oldObject);
первым аргументом этого метода является массив, подлежащий обновлению,
мы передаем пустой объект, потому что хотим иметь новый объект.
мы также можем использовать этот синтаксис, который является таким же, но короче:
let newObject = [...oldObject];
Ответ 21
Мы можем изобрести простой рекурсивный метод Array для клонирования многомерных массивов. Хотя объекты внутри вложенных массивов сохраняют ссылку на соответствующие объекты в исходном массиве, массивы не будут.
Array.prototype.clone = function(){
return this.map(e => Array.isArray(e) ? e.clone() : e);
};
var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
brr = arr.clone();
brr[4][2][1] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));
Ответ 22
Это глубоко копирует массивы, объекты, нулевые и другие скалярные значения, а также глубоко копирует любые свойства для нелокальных функций (что довольно редко, но возможно). (Для эффективности мы не пытаемся скопировать нечисловые свойства на массивы.)
function deepClone (item) {
if (Array.isArray(item)) {
var newArr = [];
for (var i = item.length; i-- > 0;) {
newArr[i] = deepClone(item[i]);
}
return newArr;
}
if (typeof item === 'function' && !(/\(\) \{ \[native/).test(item.toString())) {
var obj;
eval('obj = '+ item.toString());
for (var k in item) {
obj[k] = deepClone(item[k]);
}
return obj;
}
if (item && typeof item === 'object') {
var obj = {};
for (var k in item) {
obj[k] = deepClone(item[k]);
}
return obj;
}
return item;
}
Ответ 23
с jQuery:
var target= [];
$.each(source, function() {target.push( $.extend({},this));});
Ответ 24
Следующий код будет выполнять рекурсивно глубокое копирование объектов и массива:
function deepCopy(obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
var out = [], i = 0, len = obj.length;
for ( ; i < len; i++ ) {
out[i] = arguments.callee(obj[i]);
}
return out;
}
if (typeof obj === 'object') {
var out = {}, i;
for ( i in obj ) {
out[i] = arguments.callee(obj[i]);
}
return out;
}
return obj;
}
Источник
Ответ 25
Некоторые изящные способы глубокого клонирования в javascript
https://mootools.net/core/docs/1.6.0/Types/Object
https://scotch.io/bar-talk/copying-objects-in-javascript
1) Метод ванильного JavaScript для клонирования объектов
2) Умный эксплойт библиотеки JSON для глубоких клонированных объектов
3) Использование функции jQuerys $.extend()
4) Использование функции клонирования Mootools() для клонирования объектов
Ответ 26
Мне кажется, удалось написать общий метод глубокого клонирования любой структуры JavaScript, в основном используя Object.create
, который поддерживается во всех современных браузерах. Код выглядит так:
function deepClone (item) {
if (Array.isArray(item)) {
var newArr = [];
for (var i = item.length; i-- !== 0;) {
newArr[i] = deepClone(item[i]);
}
return newArr;
}
else if (typeof item === 'function') {
eval('var temp = '+ item.toString());
return temp;
}
else if (typeof item === 'object')
return Object.create(item);
else
return item;
}
Ответ 27
Для клонирования объектов я просто собирался предложить ECMAScript 6 reduce()
:
const newArray=myArray.reduce((array, element)=>array.push(Object.assign({}, element)), []);
Но, честно говоря, мне больше нравится ответ @dinodsaurus. Я просто помещаю эту версию здесь как другой вариант, но лично я буду использовать map()
, как было предложено @dinodsaurus.
Ответ 28
В зависимости от того, у вас есть Underscore или Babel, это контрольный параметр для различного способа глубокого клонирования массива.
https://jsperf.com/object-rest-spread-vs-clone/2
Похоже, что babel самый быстрый.
var x = babel({}, obj)
Ответ 29
var game_popularity = [
{ game: "fruit ninja", popularity: 78 },
{ game: "road runner", popularity: 20 },
{ game: "maze runner", popularity: 40 },
{ game: "ludo", popularity: 75 },
{ game: "temple runner", popularity: 86 }
];
console.log("sorted original array before clonning");
game_popularity.sort((a, b) => a.popularity < b.popularity);
console.log(game_popularity);
console.log("clone using object assign");
const cl2 = game_popularity.map(a => Object.assign({}, a));
cl2[1].game = "clash of titan";
cl2.push({ game: "logan", popularity: 57 });
console.log(cl2);
//adding new array element doesnt reflect in original array
console.log("clone using concat");
var ph = []
var cl = ph.concat(game_popularity);
//copied by reference ?
cl[0].game = "rise of civilization";
game_popularity[0].game = 'ping me';
cl.push({ game: "angry bird", popularity: 67 });
console.log(cl);
console.log("clone using ellipses");
var cl3 = [...game_popularity];
cl3.push({ game: "blue whale", popularity: 67 });
cl3[2].game = "harry potter";
console.log(cl3);
console.log("clone using json.parse");
var cl4 = JSON.parse(JSON.stringify(game_popularity));
cl4.push({ game: "home alone", popularity: 87 });
cl4[3].game ="lockhead martin";
console.log(cl4);
console.log("clone using Object.create");
var cl5 = Array.from(Object.create(game_popularity));
cl5.push({ game: "fish ville", popularity: 87 });
cl5[3].game ="veto power";
console.log(cl5);
//array function
console.log("sorted original array after clonning");
game_popularity.sort((a, b) => a.popularity < b.popularity);
console.log(game_popularity);
console.log("Object.assign deep clone object array");
console.log("json.parse deep clone object array");
console.log("concat does not deep clone object array");
console.log("ellipses does not deep clone object array");
console.log("Object.create does not deep clone object array");
Output:
sorted original array before clonning
[ { game: 'temple runner', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 } ]
clone using object assign
[ { game: 'temple runner', popularity: 86 },
{ game: 'clash of titan', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'logan', popularity: 57 } ]
clone using concat
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'ludo', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'angry bird', popularity: 67 } ]
clone using ellipses
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'maze runner', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'blue whale', popularity: 67 } ]
clone using json.parse
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'lockhead martin', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'home alone', popularity: 87 } ]
clone using Object.create
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'veto power', popularity: 40 },
{ game: 'road runner', popularity: 20 },
{ game: 'fish ville', popularity: 87 } ]
sorted original array after clonning
[ { game: 'ping me', popularity: 86 },
{ game: 'fruit ninja', popularity: 78 },
{ game: 'harry potter', popularity: 75 },
{ game: 'veto power', popularity: 40 },
{ game: 'road runner', popularity: 20 } ]
Object.assign deep clone object array
json.parse deep clone object array
concat does not deep clone object array
ellipses does not deep clone object array
Object.create does not deep clone object array
Ответ 30
В JavaScript копирование массивов и объектов изменяет исходные значения, поэтому Deep Copy является решением для этого.
Глубокая копия означает на самом деле создание нового массива и копирование значений, поскольку все, что происходит с ним, никогда не повлияет на исходный.
JSON.parse
и JSON.stringify
- лучший и простой способ глубокого копирования. Метод JSON.stringify()
преобразует значение JavaScript в строку JSON. Метод JSON.parse()
анализирует строку JSON, создавая значение JavaScript или объект, описываемый строкой.
//Глубокий клон
let a = [{ x:{z:1} , y: 2}];
let b = JSON.parse(JSON.stringify(a));
b[0].x.z=0
console.log(JSON.stringify(a)); //[{"x":{"z":1},"y":2}]
console.log(JSON.stringify(b)); // [{"x":{"z":0},"y":2}]
Для более подробной информации: читайте здесь