Каков наиболее эффективный способ клонирования объекта JavaScript? Я видел использование obj = eval(uneval(o));, но это нестандартно и поддерживается только Firefox.
Я делал такие вещи, как obj = JSON.parse(JSON.stringify(o));, но сомневаюсь в эффективности.
Я также видел функции рекурсивного копирования с различными недостатками.
Я удивлен, что канонического решения не существует.
Клонирование объектов - непростая задача, особенно с пользовательскими объектами из произвольных коллекций. Вероятно, поэтому нет готового способа сделать это.
eval() - вообще плохая идея, потому что многие оптимизаторы движка Javascript должны отключаться при работе с переменными, которые установлены через eval.. Просто наличие eval() в вашем коде может привести к снижению производительности.
Возможный дубликат Самый элегантный способ клонировать объект JavaScript
вот сравнение производительности наиболее распространенных типов объектов клонирования: jsben.ch/#/t917Z
Обратите внимание, что метод JSON потеряет все типы Javascript, которые не имеют эквивалента в JSON. Например: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false})) сгенерирует {a: null, b: null, c: null, g: false}
Сообщество React представило неизменность-помощник



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Если бы встроенного не было, можно попробовать:
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;
}
function clone(obj)
{ var clone = {};
clone.prototype = obj.prototype;
for (property in obj) clone[property] = obj[property];
return clone;
}
Это называется «структурированное клонирование», экспериментально работает в Node 11 и более поздних версиях и, надеюсь, появится в браузерах. Подробнее см. этот ответ.
Если вы не используете Date, функции, undefined, Infinity, регулярные выражения, карты, наборы, большие двоичные объекты, списки файлов, ImageDatas, разреженные массивы, типизированные массивы или другие сложные типы в вашем объекте, очень простой вкладыш для глубокого клонирования объекта:
JSON.parse(JSON.stringify(object))
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // stringified
undef: undefined, // lost
inf: Infinity, // forced to 'null'
re: /.*/, // lost
}
console.info(a);
console.info(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.info(clone);
console.info(typeof clone.date); // result of .toISOString()См. Тесты Ответ Корбана.
Поскольку клонирование объектов нетривиально (сложные типы, циклические ссылки, функции и т. д.), Большинство основных библиотек предоставляют функции для клонирования объектов. Не изобретайте велосипед - если вы уже используете библиотеку, проверьте, есть ли в ней функция клонирования объекта. Например,
cloneDeep; можно импортировать отдельно через модуль lodash.clonedeep, и это, вероятно, ваш лучший выбор, если вы еще не используете библиотеку, которая предоставляет функцию глубокого клонированияangular.copyjQuery.extend(true, { }, oldObject); .clone() только клонирует элементы DOMДля полноты заметим, что 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!
@GabrielHautclocq какое решение в этом случае?
@GabrielHautclocq Вы только что узнали, что означает «неглубокая копия». "@medBouzid" Решение состоит в "глубоком копировании" объекта вместо поверхностного копирования.
Я просто продемонстрировал на примере, что нельзя использовать Object.assign для глубокого клонирования объекта.
Object.assign() действительно бесполезен, если ваш объект для копирования содержит в нем массивы, особенно массивы объектов. Просто выяснил это на собственном горьком опыте ...
@Gabriel Hautclocq, это потому, что A.b или B.b ссылаются на один и тот же объект в памяти. если A имеет свойство с не объектным значением (например, числа или строки), оно будет скопировано обычным образом. Но когда копируется свойство, содержащее значение объекта, оно копируется по ссылке, а не по значению. Также имейте в виду, что массив - это объект в JS. доказательство: typeof [] == 'object' && [] instanceof Array
@Unicornist Да, и именно поэтому Object.assign не отвечает на вопрос: «Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript?». Так что, по крайней мере, его НЕ следует представлять как решение ES6 для глубокого клонирования. Название «ES6» вводит в заблуждение, по крайней мере, его следует изменить, чтобы отразить, что это не метод глубокого клонирования. Слово «мелкий» легко упустить из виду, и многие люди просто берут самое простое решение, которое они находят в Stack Overflow, не читая все подряд. Опасно полагаться на Object.assign при клонировании объекта. Отсюда и мое замечание.
Я использовал библиотеку под названием «действительно быстрый глубокий клон»: github.com/davidmarkclements/rfdc Мне очень понравилось.
Если у вас возникнут проблемы, как в первом комментарии, github.com/davidmarkclements/rfdc также сделает «мелкую копию», вам необходимо включить опцию proto: true,
Код:
// 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;
}
var clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
}
else
{
newObj[i] = this[i];
}
}
return newObj;
};
Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
// obj target object, vals source object
var setVals = function (obj, vals) {
if (obj && vals) {
for (var x in vals) {
if (vals.hasOwnProperty(x)) {
if (obj[x] && typeof vals[x] === 'object') {
obj[x] = setVals(obj[x], vals[x]);
} else {
obj[x] = vals[x];
}
}
}
}
return obj;
};
Крокфорд предлагает (и я предпочитаю) использовать эту функцию:
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
Кажется, что еще не существует идеального оператора глубокого клонирования для объектов, подобных массиву. Как показано в приведенном ниже коде, 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)
Предполагая, что у вас есть только переменные, а не какие-либо функции в вашем объекте, вы можете просто использовать:
var newObject = JSON.parse(JSON.stringify(oldObject));
У объектов есть свойства, а не переменные. ;-)
Проверить этот тест: http://jsben.ch/#/bWfk9
В своих предыдущих тестах, где главной проблемой была скорость, я обнаружил, что
JSON.parse(JSON.stringify(obj))
чтобы быть самым медленным способом глубокого клонирования объекта (он медленнее, чем jQuery.extend с флагом deep, установленным на 10-20%).
jQuery.extend работает довольно быстро, если для флага deep установлено значение false (неглубокий клон). Это хороший вариант, потому что он включает некоторую дополнительную логику для проверки типа и не копирует неопределенные свойства и т. д., Но это также немного замедлит вас.
Если вы знаете структуру объектов, которые пытаетесь клонировать, или можете избежать глубоких вложенных массивов, вы можете написать простой цикл for (var i in obj) для клонирования вашего объекта при проверке hasOwnProperty, и это будет намного быстрее, чем jQuery.
Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить НАМНОГО БОЛЬШЕ ПРОИЗВОДИТЕЛЬНОСТИ, просто встроив процедуру клонирования и создав объект вручную.
Механизмы трассировки JavaScript плохо справляются с оптимизацией циклов for..in, а проверка hasOwnProperty также замедлит вас. Ручное клонирование, когда скорость абсолютно необходима.
var clonedObject = {
knownProp: obj.knownProp,
..
}
Остерегайтесь использования метода JSON.parse(JSON.stringify(obj)) для объектов Date - JSON.stringify(new Date()) возвращает строковое представление даты в формате ISO, которое JSON.parse()не преобразует обратно в объект Date. См. Этот ответ для получения более подробной информации..
Кроме того, обратите внимание, что, по крайней мере, в Chrome 65 встроенное клонирование не подходит. Согласно JSPerf, выполнение нативного клонирования путем создания новой функции почти на 800x медленнее, чем при использовании JSON.stringify, который невероятно быстр во всех направлениях.
Если вы используете Javascript ES6, попробуйте этот собственный метод клонирования или неглубокого копирования.
Object.assign({}, obj);
Обратите внимание, что в вашем тесте есть 2 ошибки: во-первых, он сравнивает некоторое поверхностное клонирование (lodash _.clone и Object.assign) с некоторым глубоким клонированием (JSON.parse(JSON.stringify())). Во-вторых, он говорит «глубокий клон» для lodash, но вместо этого делает неглубокий клон.
Я думаю, что это лучшее решение, если вы хотите обобщить свой алгоритм клонирования объектов. Его можно использовать с 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);
}
Обычно это не самое эффективное решение, но оно делает то, что мне нужно. Ниже приведены простые тестовые примеры ...
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
Это самый быстрый из созданных мной методов, который не использует прототип, поэтому он будет поддерживать 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.info(objNew['0']) === 'NEW VALUE';
console.info(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
Я знаю, что это старый пост, но я подумал, что он может немного помочь следующему человеку, который спотыкается.
Пока вы не назначаете объект чему-либо, он не сохраняет ссылки в памяти. Итак, чтобы создать объект, которым вы хотите поделиться с другими объектами, вам нужно создать такую фабрику:
var a = function(){
return {
father:'zacharias'
};
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Если вы его используете, в библиотеке Underscore.js есть метод клон.
var newObject = _.clone(oldObject);
Стандарт HTML включает внутренний структурированный алгоритм клонирования / сериализации, который может создавать глубокие клоны объектов. Он по-прежнему ограничен некоторыми встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает даты, регулярные выражения, карты, наборы, большие двоичные объекты, списки файлов, ImageDatas, разреженные массивы, типизированные массивы и, возможно, многое другое в будущем. . Он также сохраняет ссылки в клонированных данных, позволяя поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.
Модуль 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.info("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.info("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.info("Assertions complete.");
};
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.close();
return n.data;
};
main();Это так неправильно! Этот API не предназначен для использования таким образом.
Как человек, реализовавший pushState в Firefox, я испытываю странную смесь гордости и отвращения от этого взлома. Молодцы ребята.
Взлом pushState или Notification не работает для некоторых типов объектов, таких как Function
@ShishirArora Вы правы, я только что попробовал, выдает исключение Uncaught DOMException: объект не может быть клонирован. Это также верно для взлома уведомлений.
Мелкая однострочная копия (ECMAScript 5-е издание):
var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});
console.info(origin, copy);
console.info(origin == copy); // false
console.info(origin.foo == copy.foo); // true
И мелкая копия однострочного (ECMAScript 6-е издание, 2015):
var origin = { foo : {} };
var copy = Object.assign({}, origin);
console.info(origin, copy);
console.info(origin == copy); // false
console.info(origin.foo == copy.foo); // true
Вот комплексный метод 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;
};
Он преобразует примитивы в объекты-оболочки, что в большинстве случаев не является хорошим решением.
@DanubianSailor - я не думаю, что это так ... кажется, он возвращает примитивы сразу с самого начала и, похоже, не делает с ними ничего, что могло бы превратить их в объекты-оболочки по мере их возврата.
Есть библиотека (так называемая «клон»), который с этим справляется. Он обеспечивает наиболее полное рекурсивное клонирование / копирование произвольных объектов, о которых я знаю. Он также поддерживает циклические ссылки, которые пока не рассматриваются в других ответах.
Вы тоже можете найди это на npm. Его можно использовать как в браузере, так и в Node.js.
Вот пример того, как его использовать:
Установите его с помощью
npm install clone
или упакуйте его с помощью Эндер.
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.info(a); // { foo: { bar: 'foo' } }
console.info(b); // { foo: { bar: 'baz' } }
(Отказ от ответственности: я являюсь автором библиотеки.)
Вот версия ответа 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;
}
Эта функция также доступна в моей библиотеке простооо.
Редактировать:
Вот более надежная версия (благодаря Джастину МакКэндлессу теперь также поддерживаются циклические ссылки):
/**
* 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();
};
}
У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект 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.
Этот код не только краток, но и очень удобен для чтения. Довольно легко расширить.
Это эффективно? Черт возьми, да. Учитывая, что цель состоит в том, чтобы создать настоящий клон с глубокой копией, вам придется пройтись по членам графа исходного объекта. При таком подходе вы можете точно настроить, какие дочерние элементы обрабатывать и как вручную обрабатывать настраиваемые типы.
Итак, поехали. Два подхода. На мой взгляд, оба они эффективны.
У Lodash есть хороший метод _.cloneDeep (значение):
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.info(deep[0] === objects[0]);
// => false
Ответов много, но ни один из них не дал желаемого эффекта. Я хотел использовать возможности глубокой копии 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
Смотрите устаревшие браузеры!
Для второго способа нужен прототип, я предпочитаю первый, даже если он не самый лучший по производительности из-за того, что вы можете использовать его с большим количеством браузеров и с Node JS.
Это круто, но предположим, что у o есть свойство a. Теперь есть oo.hasOwnProperty ('a')?
No-o по сути добавлен как прототип oo. Скорее всего, это не будет желаемым поведением, поэтому 99,9% методов serialize (), которые я пишу, используют подход JSON, упомянутый выше. Я в основном всегда использую JSON, и есть другие предостережения при использовании Object.create.
Нет, посмотрите этот код! Object.create не обязательно создавать копию объекта, вместо этого он использует более старый объект в качестве прототипа для клона.
Это моя версия клонирования объектов. Это автономная версия метода 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"}
В моем случае я действительно ожидал перезаписать существующие элементы, поэтому текущее поведение было правильным для этого использования. Но спасибо, что добавили это полезное предложение.
Для дальнейшего использования, текущий черновик ECMAScript 6 представляет Object.assign как способ клонирования объектов. Пример кода:
var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.info(obj2); // { a: true, b: 1 }
На момент написания поддержка ограничена Firefox 34 в браузерах, поэтому его еще нельзя использовать в производственном коде (если, конечно, вы не пишете расширение Firefox).
Вы, наверное, имели в виду obj2 = Object.assign({}, obj1). Ваш текущий код эквивалентен obj2 = obj1.
Это мелкий клон. const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456;, теперь o1.a.deep === 456 тоже.
Object.assign() не предназначен для клонирования вложенных объектов.
Ого, еще один бесполезный ответ. Взято из MDN developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…: Warning for Deep Clone - For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.
Глубокая копия по производительности: От лучших к худшим
Глубоко скопируйте массив строк или чисел (один уровень - без ссылочных указателей):
Когда массив содержит числа и строки - такие функции, как .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 имеет лучшую производительность:
Используйте 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;
}
Отличный ответ! Я думаю, что это один из немногих способов сохранить нетронутыми сеттеры и геттеры. Это решило мою проблему. Спасибо! (см .: stackoverflow.com/questions/33207028/…)
Разве вы не хотели бы использовать clone = Object.create(Object.getPrototypeOf(source)) вместо наследования свойств, которые вы также перезаписываете?
Интересно. Однако использование getPrototypeOf на Array превращает его индексы в ключи нового Object.
Требуются новые браузеры, но ...
Давайте расширим собственный объект и получим настоящий.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.info(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
Изменение прототипов обычно считается плохой практикой, за исключением прокладок.
Следующее создает два экземпляра одного и того же объекта. Я нашел и использую сейчас. Это просто и удобно.
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Только когда вы можете использовать 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)), но без потери объектов 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.info(cloned)Не совсем 100% клон
Метод Object.assign является частью стандарта ECMAScript 2015 (ES6) и делает именно то, что вам нужно.
var clone = Object.assign({}, obj);
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
полифил для поддержки старых браузеров:
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;
}
});
}
Это не рекурсивное копирование, поэтому на самом деле не предлагает решения проблемы клонирования объекта.
Этот метод работал, хотя я тестировал несколько, и _.extend ({}, (obj)) был НАМНОГО быстрее всех: в 20 раз быстрее, чем JSON.parse, и на 60% быстрее, чем, например, Object.assign. Он неплохо копирует все подобъекты.
@mwhite есть разница между clone и deep-clone. Этот ответ на самом деле клонирует, но не глубоко клонирует.
вопрос был о рекурсивных копиях. Object.assign, как и данное настраиваемое назначение, не копирует рекурсивно
Поскольку рекурсия слишком дорога для 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
Рекурсия хвостового вызова на самом деле очень эффективна в большинстве реализаций JavaScript и требует оптимизации в ES6.
Привет, я тогда провел небольшой тест, и когда целевой объект станет сложным, стек вызовов легко переполнится, хотя я не делал никаких заметок, надеюсь, в es6 это будет большая операция.
Стек легко переполнится, вероятно, из-за циклической ссылки.
Клонирование объекта с использованием сегодняшнего 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, это всего лишь поверхностная копия. Свойства свойств копируются как ссылка: изменение одного из них приведет к изменению значения в другом объекте / экземпляре.
Это не касается глубоких слияний. gist.github.com/jimbol/5d5a3e3875c34abcf60a
Вау, это такой неправильный ответ. Оба ваших метода делают мелкую копию одного уровня. Любой, кто смотрит на этот ответ, продолжайте.
Просто потому, что я не видел упоминания о AngularJS и подумал, что люди могут захотеть узнать ...
angular.copy также предоставляет метод глубокого копирования объектов и массивов.
или его можно использовать так же, как расширение jQuery: angular.extend({},obj);
@Galvani: Следует отметить, что jQuery.extend и angular.extend являются мелкими копиями. angular.copy - это глубокая копия.
Однострочное решение 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.info(x.nested !== y.nested);Пожалуйста, предоставьте объяснение вместе с фрагментом кода, чтобы другие, у кого возник аналогичный вопрос, могли легко понять, что происходит. В настоящее время этот вопрос находится в очереди на рассмотрение сообщений низкого качества.
Пожалуйста, отредактируйте с дополнительной информацией. Ответы только на код и "попробуйте это" не приветствуются, потому что они не содержат доступного для поиска контента и не объясняют, почему кто-то должен "попробовать это".
Я использую библиотеку клонов npm. Видимо в браузере тоже работает.
https://www.npmjs.com/package/clone
let a = clone(b)
Что ж, если вы используете angular, вы тоже можете это сделать
var newObject = angular.copy(oldObject);
Это решение с рекурсией:
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.info(Clone(obj))полностью терпит неудачу при простом тесте [1,2]
Для справки в будущем можно использовать этот код
ES6:
_clone: function(obj){
let newObj = {};
for(let i in obj){
if (typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
ES5:
function clone(obj){
let newObj = {};
for(let i in obj){
if (typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
Например
var obj = {a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.info(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.info(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;
console.info(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.info(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
это не обрабатывает массивы, которые также являются объектами.
class Handler {
static deepCopy (obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
const result = [];
for (let i = 0, len = obj.length; i < len; i++) {
result[i] = Handler.deepCopy(obj[i]);
}
return result;
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
const result = {};
for (let prop in obj) {
result[prop] = Handler.deepCopy(obj[prop]);
}
return result;
}
return obj;
}
}Cloning Объект всегда был проблемой в JS, но все это было до ES6, я перечисляю различные способы копирования объекта в JavaScript ниже, представьте, что у вас есть объект ниже, и вы хотели бы получить его полную копию:
var obj = {a:1, b:2, c:3, d:4};
Есть несколько способов скопировать этот объект без изменения начала координат:
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.");
}
var deepCopyObj = JSON.parse(JSON.stringify(obj));
var deepCopyObj = angular.copy(obj);
var deepCopyObj = jQuery.extend(true, {}, obj);
var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy
Надеюсь, это поможет ...
Не касаясь прототипического наследования, вы можете углубить одинокие объекты и массивы следующим образом;
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.info(clobj, obj === clobj);
console.info(clarr, arr === clarr);
console.info(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.info(arr, clarr);В Lodash есть функция, которая вам это нравится.
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}
Прочтите документы здесь.
В итоге я использовал это, поскольку JSON.parse (JSON.stringify (obj)) не сохраняет исходный прототип объекта.
Это мой ответ goto. За исключением того, что я использую слияние Lodash, синтаксис остается в некоторой степени согласованным для глубокого и неглубокого копирования. //Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
Я не согласен с ответом, набравшим наибольшее количество голосов здесь. Рекурсивный глубокий клон - это намного быстрее, чем упомянутый подход JSON.parse (JSON.stringify (obj)).
А вот функция для быстрого ознакомления:
function cloneDeep (o) {
let newO
let i
if (typeof o !== 'object') return o
if (!o) return o
if (Object.prototype.toString.apply(o) === '[object Array]') {
newO = []
for (i = 0; i < o.length; i += 1) {
newO[i] = cloneDeep(o[i])
}
return newO
}
newO = {}
for (i in o) {
if (o.hasOwnProperty(i)) {
newO[i] = cloneDeep(o[i])
}
}
return newO
}
Мне понравился этот подход, но он не обрабатывает даты должным образом; подумайте о добавлении чего-то вроде if (o instanceof Date) return new Date(o.valueOf()); после проверки на null `
Сбои при циклических ссылках.
В последней стабильной версии Firefox это намного дольше, чем другие стратегии по этой ссылке Jsben.ch, на порядок или больше. Это побеждает других в неправильном направлении.
Поскольку этому вопросу уделяется много внимания, и на него есть ответы в отношении встроенных функций, таких как 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.info(fooClone)
console.info(foo === fooClone)
2. глубокая копия
npm установить глубокую копию https://www.npmjs.com/package/deep-copy
Пример:
var dcopy = require('deep-copy')
// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})
// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])
3. глубокий клон
$ npm install --save clone-deep https://www.npmjs.com/package/clone-deep
Пример:
var cloneDeep = require('clone-deep');
var obj = {a: 'b'};
var arr = [obj];
var copy = cloneDeep(arr);
obj.c = 'd';
console.info(copy);
//=> [{a: 'b'}]
console.info(arr);
Есть так много способов добиться этого, но если вы хотите сделать это без какой-либо библиотеки, вы можете использовать следующее:
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;
}
Дайте мне знать, что вы думаете.
Я опаздываю с ответом на этот вопрос, но у меня есть другой способ клонирования объекта:
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
это забавно, но когда я провожу ваши тесты, они действительно показали мне, что метод 1 самый медленный
как и я, блок 1 самый низкий!
Единственное решение, которое сработало для меня! Пришлось глубоко клонировать объект, содержащий другие объекты со свойствами функции. Идеально.
Вот мой способ глубокого клонирования объекта со значением по умолчанию 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.info(makeDeepCopy(testObj))Объект тура test - слишком наивный. он должен иметь некоторые undefined, функции, даты и null, а не просто набор строк
А как насчет асинхронного клонирования объектов, выполняемого Promise?
async function clone(thingy /**/)
{
if (thingy instanceof Promise)
{
throw Error("This function cannot clone Promises.");
}
return thingy;
}
Постойте, 5 сторонников, как это работает? Я сам забыл об этом, и теперь, когда прошло полтора года, это выглядит нелогично.
Не знаю, что он должен делать, я в замешательстве: s
Promise.resolve(value) разрешает клонированный value? Я сомневаюсь в этом, мимо меня.
Просматривая этот длинный список ответов, были рассмотрены почти все решения, кроме одного, о котором я знаю. Это список способов VANILLA JS для глубокого клонирования объекта.
JSON.parse (JSON.stringify (obj));
Через history.state с помощью pushState или replaceState
API веб-уведомлений, но у этого есть обратная сторона - запрашивать разрешения у пользователя.
Выполнение собственного рекурсивного цикла по объекту для копирования каждого уровня.
Ответа я не видел -> Использование ServiceWorkers. Сообщения (объекты), передаваемые между страницей и сценарием ServiceWorker, будут глубокими клонами любого объекта.
Все это уже преобразовано либо в ответах, либо в комментариях. Я бы проголосовал за это, если бы вы дали уникальные примеры кода для каждого из них.
Пример ES 2017:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
Спасибо за ответ. Я пробовал ваш подход, но, к сожалению, он не работает. Поскольку это может быть какая-то ошибка с моей стороны, я прошу вас проверить мой пример в JSFiddle, и если это будет какая-то ошибка с моей стороны, я проголосую за ваш ответ.
Когда я запускаю вашу скрипку, я получаю { foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3} и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}. Разве это не то, чего вы ожидаете?
то, что вы вставили, - это то, что я ожидал. Я не понимаю почему, но я вижу fooBaz: 44 для testObj2 и testObj3 в консоли ... (Скриншот)
Это не глубокая копия, а мелкая копия. @GurebuBokofu
По моему опыту, рекурсивная версия значительно превосходит 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 (неглубокое копирование невозможно), функция рекурсивно вызывает себя со значением в качестве аргумента.
Жаль, что это не работает правильно, когда значение представляет собой массив. Но не должно быть слишком сложно изменить, чтобы заставить его работать в этом случае.
TypeError: невозможно прочитать свойство 'constructor' неопределенного
Для мелкой копии в стандарте ECMAScript2018 есть отличный простой метод. Это предполагает использование Оператор распространения:
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
Я тестировал его в браузере Chrome, оба объекта хранятся в разных местах, поэтому изменение непосредственных дочерних значений в одном из них не изменит другого. Хотя (в примере) изменение значения в e повлияет на обе копии.
Этот метод очень прост и понятен. Я считаю, что это лучшая практика для ответа на этот вопрос раз и навсегда.
обновление e в objClone по-прежнему будет обновлять e в obj. Это пока лишь поверхностная копия. Вопрос явно требует глубокого клона.
@Taugenichts ... ты это тестировал? Метод работает отлично. Spread_syntaxSpread in object literals раздел
да, я это тестировал. запустите этот код: objClone.e [4] = 5; console.info (obj.e); Вы увидите, что obj.e обновляется
Поскольку оба хранятся в разных местах, это просто означает, что это, по крайней мере, неглубокая копия. Посмотрите, где хранятся obj.e и objClone.e; вы обнаружите, что они хранятся в одном месте.
Большое спасибо, ребята @ LupusOssorum @Taugenichts, что указали на это. Я сам это проверил и выяснил, что вы здесь определили. Но знаете ли вы, почему массив до сих пор не изменяет память, хотя ECMA2018 может похвастаться этим как функцией.
Из документации mozilla в разделе «Распространение в литералах объектов»: «Поверхностное клонирование (за исключением прототипа) или слияние объектов теперь возможно с использованием более короткого синтаксиса, чем Object.assign ()». - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Надеюсь это поможет.
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;
}
Глубокое копирование объектов в JavaScript (считаю лучшим и самым простым)
1. Использование JSON.parse (JSON.stringify (объект));
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.info(obj); // { a: 1, b: { c: 20 } }
console.info(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.info(obj); // { a: 1, b: { c: 20 } }
console.info(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.info(obj); // { a: 1, b: { c: 20 } }
console.info(newObj); // { a: 1, b: { c: 2 } }
4. Использование метода Object.assign ()
var obj = {
a: 1,
b: 2
}
var newObj = _.clone(obj);
obj.b = 20;
console.info(obj); // { a: 1, b: 20 }
console.info(newObj); // { a: 1, b: 2 }
НО НЕПРАВИЛЬНО, КОГДА
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.info(obj); // { a: 1, b: { c: 20 } }
console.info(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.info(obj); // { a: 1, b: 20 }
console.info(newObj); // { a: 1, b: 2 }
НО НЕПРАВИЛЬНО, КОГДА
var obj = {
a: 1,
b: {
c: 2
}
}
var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.info(obj); // { a: 1, b: { c: 20 } }
console.info(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
Эй, твой последний пример неверен. На мой взгляд, для неправильного примера вы должны использовать _clone, а не _cloneDeep.
Этот созданный метод (2.) не будет работать с массивами, не так ли?
Метод № 2 уязвим для загрязнения прототипа, подобно тому, что случилось с defaultsDeep от lodash. Он не должен копировать, если (i === '__proto__'), и он не должен копировать, если (i === 'constuctor' && typeof obj[i] === 'function').
Если ваш объект является вложенным и содержит объект данных, другой структурированный объект или какой-либо объект свойств и т. д., Тогда использование 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.info( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"
In JavaScript, you can write your
deepCopymethod like
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__')
Как насчет объединения ключи объекта с его значения?
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 является клоном не ссылка.
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)), потому что
это медленнее, чем следующий метод
С предложением нового метода 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.info("Original object: ", obj);
console.info("Cloned object: ", newObj);.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}Мой сценарий был немного другим. У меня был объект с вложенными объектами, а также с функциями. Следовательно, 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.info('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.info('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.info(a); // updated props
console.info(b);
Что касается проблем, связанных с эффективностью, я считаю, что это самое простое и эффективное решение возникшей у меня проблемы. Буду признателен за любые комментарии по этому алгоритму, которые могли бы сделать его более эффективным.
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); -> ошибка рекурсии.
Эвал не зло. Плохое использование eval. Если вы боитесь его побочных эффектов, вы неправильно его используете. Побочные эффекты, которых вы боитесь, - это причины для его использования. Кстати, действительно ли кто-нибудь ответил на ваш вопрос?