Как глубоко клон в javascript
Как вы глубоко клонируете объект Javascript?
Я знаю, что существуют различные функции на основе таких фреймворков, как JSON.parse(JSON.stringify(o))
и $.extend(true, {}, o)
, но я не хочу использовать такую фреймворк.
Каков самый элегантный или эффективный способ создания глубокого клона.
Мы заботимся о таких случаях, как клонирование массива. Не нарушать цепи прототипов, имея дело с самостоятельной ссылкой.
Мы не заботимся о поддержке копирования объектов DOM и, например, потому что .cloneNode
существует по этой причине.
В основном я хочу использовать глубокие клоны в node.js
, используя возможности ES5 для двигателя V8.
[изменить]
Прежде, чем кто-нибудь предложит, позвольте мне упомянуть, что существует отличная разница между созданием копии путем прототипного наследования с объекта и клонирования. Первый делает беспорядок цепи прототипов.
[Дальнейшее редактирование]
После прочтения вашего ответа я пришел к раздражающему открытию, что клонирование целых объектов - очень опасная и трудная игра. Возьмем, к примеру, следующий объект, основанный на закрытии
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
Есть ли способ написать функцию клонирования, которая клонирует объект, имеет такое же состояние во время клонирования, но не может изменить состояние o
без написания JS-парсера в JS.
Больше не должно быть необходимости в реальной жизни для такой функции. Это просто академический интерес.
Ответы
Ответ 1
Это действительно зависит от того, что вы хотели бы клонировать. Это действительно объект JSON или просто какой-либо объект в JavaScript? Если вы захотите сделать какой-нибудь клон, у вас могут возникнуть проблемы. Какая проблема? Я объясню это ниже, но сначала приведу пример кода, который клонирует литералы объектов, любые примитивы, массивы и узлы DOM.
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
var copy = clone({
one : {
'one-one' : new String("hello"),
'one-two' : [
"one", "two", true, "four"
]
},
two : document.createElement("div"),
three : [
{
name : "three-one",
number : new Number("100"),
obj : new function() {
this.name = "Object test";
}
}
]
})
А теперь давайте поговорим о проблемах, которые могут возникнуть при начале клонирования РЕАЛЬНЫХ объектов. Я говорю сейчас об объектах, которые вы создаете, делая что-то вроде
var User = function(){}
var newuser = new User();
Конечно, вы можете их клонировать, это не проблема, каждый объект предоставляет свойство конструктора, и вы можете использовать его для клонирования объектов, но это не всегда будет работать. Вы также можете сделать простой for in
этого на объектах, но это идет в том же направлении - неприятности. Я также включил в код функциональность клонирования, но он исключен оператором if( false )
.
Итак, почему клонирование может быть болью? Ну, во-первых, у каждого объекта/экземпляра может быть какое-то состояние. Вы никогда не можете быть уверены, что ваши объекты не имеют, например, приватных переменных, и если это так, клонируя объект, вы просто нарушаете состояние.
Представьте, что нет государства, это хорошо. Тогда у нас все еще есть другая проблема. Клонирование с помощью метода "конструктор" даст нам еще одно препятствие. Это зависимость аргументов. Вы никогда не можете быть уверены, что тот, кто создал этот объект, не сделал, какой-то
new User({
bike : someBikeInstance
});
Если это так, вам не повезло, возможно, что someBikeInstance был создан в некотором контексте, и этот контекст неизвестен для метода клонирования.
Так что делать? Вы все еще можете делать это for in
решении и обрабатывать такие объекты как обычные литералы объектов, но, может быть, это идея не клонировать такие объекты вообще, а просто передать ссылку на этот объект?
Другое решение - вы можете установить соглашение, согласно которому все объекты, которые должны быть клонированы, должны реализовывать эту часть самостоятельно и предоставлять соответствующий метод API (например, cloneObject). Что-то, что делает cloneNode
для DOM.
Вам решать.
Ответ 2
Очень простой способ, может быть, слишком простой:
var cloned = JSON.parse(JSON.stringify(objectToClone));
Ответ 3
Комбинация JSON.parse(JSON.stringify())
для глубокого копирования объектов Javascript является неэффективным хаком, так как она предназначена для данных JSON. Он не поддерживает значения undefined
или function () {}
и будет просто игнорировать их (или null
их) при "строковом преобразовании" (маршалинге) объекта Javascript в JSON.
Лучшее решение - использовать функцию глубокого копирования. Функция ниже глубоко копирует объекты и не требует сторонней библиотеки (jQuery, LoDash и т.д.).
function copy(aObject) {
if (!aObject) {
return aObject;
}
let v;
let bObject = Array.isArray(aObject) ? [] : {};
for (const k in aObject) {
v = aObject[k];
bObject[k] = (typeof v === "object") ? copy(v) : v;
}
return bObject;
}
Ответ 4
Вот функция ES6, которая также будет работать для объектов с циклическими ссылками:
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (obj instanceof Set) return new Set(obj); // See note about this!
if (hash.has(obj)) return hash.get(obj); // cyclic reference
const result = obj instanceof Date ? new Date(obj)
: obj instanceof RegExp ? new RegExp(obj.source, obj.flags)
: obj.constructor ? new obj.constructor()
: Object.create(null);
hash.set(obj, result);
if (obj instanceof Map)
Array.from(obj, ([key, val]) => result.set(key, deepClone(val, hash)) );
return Object.assign(result, ...Object.keys(obj).map (
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
// Sample data
var p = {
data: 1,
children: [{
data: 2,
parent: null
}]
};
p.children[0].parent = p;
var q = deepClone(p);
console.log(q.children[0].parent.data); // 1
Ответ 5
Библиотека библиотека Underscore.js contrib library имеет функцию snapshot, который глубоко клонирует объект
фрагмент из источника:
snapshot: function(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = _.snapshot(obj[key]);
}
}
return temp;
}
как только библиотека будет связана с вашим проектом, вызовите функцию, просто используя
_.snapshot(object);
Ответ 6
Как уже отмечали другие и другие подобные вопросы, клонирование "объекта" в общем смысле сомнительно в JavaScript.
Однако существует класс объектов, которые я называю объектами "данных", то есть объекты, созданные просто из {... }
литералов и/или простых присваиваний свойств или десериализованные из JSON, для которых целесообразно клонировать. Только сегодня я хотел искусственно раздувать данные, полученные от сервера, в 5 раз, чтобы проверить, что происходит с большим набором данных, но объект (массив) и его дочерние элементы должны были быть отдельными объектами, чтобы вещи функционировали правильно. Клонирование позволило мне сделать это, чтобы умножить мой набор данных:
return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));
Другое место, где я в конечном итоге клонирую объекты данных, - это отправка данных обратно на хост, где я хочу убрать поля состояния из объекта в модели данных перед его отправкой. Например, я мог бы хотеть удалить все поля, начинающиеся с "_" из объекта, поскольку он клонирован.
Вот код, который я в итоге написал, чтобы сделать это в общем, включая поддержку массивов и селектор для выбора, какие члены клонировать (который использует строку "путь" для определения контекста):
function clone(obj,sel) {
return (obj ? _clone("",obj,sel) : obj);
}
function _clone(pth,src,sel) {
var ret=(src instanceof Array ? [] : {});
for(var key in src) {
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key], sub;
if(sel) {
sub+=pth+"/"+key;
if(!sel(sub,key,val)) { continue; }
}
if(val && typeof(val)=='object') {
if (val instanceof Boolean) { val=Boolean(val); }
else if(val instanceof Number ) { val=Number (val); }
else if(val instanceof String ) { val=String (val); }
else { val=_clone(sub,val,sel); }
}
ret[key]=val;
}
return ret;
}
Самое простое и разумное решение для глубокого клонирования, предполагающее ненулевой корневой объект и без выбора элемента:
function clone(src) {
var ret=(src instanceof Array ? [] : {});
for(var key in src) {
if(!src.hasOwnProperty(key)) { continue; }
var val=src[key];
if(val && typeof(val)=='object') { val=clone(val); }
ret[key]=val;
}
return ret;
}
Ответ 7
Это метод глубокого клонирования, который я использую, я думаю, это
Отлично, надеюсь, что вы внесете предложения.
function deepClone (obj) {
var _out = new obj.constructor;
var getType = function (n) {
return Object.prototype.toString.call(n).slice(8, -1);
}
for (var _key in obj) {
if (obj.hasOwnProperty(_key)) {
_out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
}
}
return _out;
}
Ответ 8
Lo-Dash, теперь расширенный набор Underscore.js, имеет несколько глубоких функций клонирования:
Из ответа самого автора:
lodash underscore
обеспечения совместимости с последней стабильной версией Underscore предусмотрена lodash underscore
.
Ответ 9
Приведенная ниже функция является наиболее эффективным способом глубокого клонирования объектов JavaScript.
function deepCopy(obj){
if (!obj || typeof obj !== "object") return obj;
var retObj = {};
for (var attr in obj){
var type = obj[attr];
switch(true){
case (type instanceof Date):
var _d = new Date();
_d.setDate(type.getDate())
retObj[attr]= _d;
break;
case (type instanceof Function):
retObj[attr]= obj[attr];
break;
case (type instanceof Array):
var _a =[];
for (var e of type){
//_a.push(e);
_a.push(deepCopy(e));
}
retObj[attr]= _a;
break;
case (type instanceof Object):
var _o ={};
for (var e in type){
//_o[e] = type[e];
_o[e] = deepCopy(type[e]);
}
retObj[attr]= _o;
break;
default:
retObj[attr]= obj[attr];
}
}
return retObj;
}
var obj = {
string: 'test',
array: ['1'],
date: new Date(),
object:{c: 2, d:{e: 3}},
function: function(){
return this.date;
}
};
var copyObj = deepCopy(obj);
console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true
Ответ 10
Я заметил, что Карта должна требовать специального лечения, поэтому со всеми предложениями в этом потоке код будет:
function deepClone( obj ) {
if( !obj || true == obj ) //this also handles boolean as true and false
return obj;
var objType = typeof( obj );
if( "number" == objType || "string" == objType ) // add your immutables here
return obj;
var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
if( obj instanceof Map )
for( var key of obj.keys() )
result.set( key, deepClone( obj.get( key ) ) );
for( var key in obj )
if( obj.hasOwnProperty( key ) )
result[key] = deepClone( obj[ key ] );
return result;
}
Ответ 11
В такой функции больше не должно быть необходимости в реальном мире. Это просто академический интерес.
Это просто более функциональное упражнение. Это расширение ответа @tfmontague, так как я предложил добавить туда защитный блок. Но, видя, что я чувствую себя обязанным к ES6 и функционализирую все, вот моя версия с сутенерством. Это усложняет логику, так как вам приходится отображать массив и уменьшать объект, но избегает любых мутаций.
function cloner(x) {
const recurseObj = x => typeof x === 'object' ? cloner(x) : x
const cloneObj = (y, k) => {
y[k] = recurseObj(x[k])
return y
}
// Guard blocks
// Add extra for Date / RegExp if you want
if (!x) {
return x
}
if (Array.isArray(x)) {
return x.map(recurseObj)
}
return Object.keys(x).reduce(cloneObj, {})
}
const tests = [
null,
[],
{},
[1,2,3],
[1,2,3, null],
[1,2,3, null, {}],
[new Date('2001-01-01')], // FAIL doesn't work with Date
{x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]},
{
obj : new function() {
this.name = "Object test";
}
} // FAIL doesn't handle functions
]
tests.map((x,i) => console.log(i, cloner(x)))
Ответ 12
мое дополнение ко всем ответам
deepCopy = arr => {
if (typeof arr !== 'object') return arr
if(arr.pop) return [...arr].map(deepCopy)
const copy = {}
for (let prop in arr)
copy[prop] = deepCopy(arr[prop])
return copy
}
Ответ 13
Это работает для массивов, объектов и примитивов. Дважды рекурсивный алгоритм, который переключается между двумя методами обхода:
const deepClone = (objOrArray) => {
const copyArray = (arr) => {
let arrayResult = [];
arr.forEach(el => {
arrayResult.push(cloneObjOrArray(el));
});
return arrayResult;
}
const copyObj = (obj) => {
let objResult = {};
for (key in obj) {
if (obj.hasOwnProperty(key)) {
objResult[key] = cloneObjOrArray(obj[key]);
}
}
return objResult;
}
const cloneObjOrArray = (el) => {
if (Array.isArray(el)) {
return copyArray(el);
} else if (typeof el === 'object') {
return copyObj(el);
} else {
return el;
}
}
return cloneObjOrArray(objOrArray);
}
Ответ 14
Мы можем использовать рекурсию для создания deepCopy. Он может создать копию массива, объекта, массива объекта, объекта с функцией. если вы хотите, вы можете добавить функцию для другого типа структуры данных, такой как карта и т.д.
function deepClone(obj) {
var retObj;
_assignProps = function(obj, keyIndex, retObj) {
var subType = Object.prototype.toString.call(obj[keyIndex]);
if(subType === "[object Object]" || subType === "[object Array]") {
retObj[keyIndex] = deepClone(obj[keyIndex]);
}
else {
retObj[keyIndex] = obj[keyIndex];
}
};
if(Object.prototype.toString.call(obj) === "[object Object]") {
retObj = {};
for(key in obj) {
this._assignProps(obj, key, retObj);
}
}
else if(Object.prototype.toString.call(obj) == "[object Array]") {
retObj = [];
for(var i = 0; i< obj.length; i++) {
this._assignProps(obj, i, retObj);
}
};
return retObj;
};
Ответ 15
Используйте immutableJS
import { fromJS } from 'immutable';
// An object we want to clone
let objA = {
a: { deep: 'value1', moreDeep: {key: 'value2'} }
};
let immB = fromJS(objA); // Create immutable Map
let objB = immB.toJS(); // Convert to plain JS object
console.log(objA); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
console.log(objB); // Object { a: { deep: 'value1', moreDeep: {key: 'value2'} } }
// objA and objB are equalent, but now they and their inner objects are undependent
console.log(objA === objB); // false
console.log(objA.a === objB.a); // false
console.log(objA.moreDeep === objB.moreDeep); // false
Или лодаш/слияние
import merge from 'lodash/merge'
var objA = {
a: [{ 'b': 2 }, { 'd': 4 }]
};
// New deeply cloned object:
merge({}, objA );
// We can also create new object from several objects by deep merge:
var objB = {
a: [{ 'c': 3 }, { 'e': 5 }]
};
merge({}, objA , objB ); // Object { a: [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
Ответ 16
Этот, используя круговую ссылку, работает для меня
//a test-object with circular reference :
var n1 = { id:0, text:"aaaaa", parent:undefined}
var n2 = { id:1, text:"zzzzz", parent:undefined }
var o = { arr:[n1,n2], parent:undefined }
n1.parent = n2.parent = o;
var obj = { a:1, b:2, o:o }
o.parent = obj;
function deepClone(o,output){
if(!output) output = {};
if(o.______clone) return o.______clone;
o.______clone = output.______clone = output;
for(var z in o){
var obj = o[z];
if(typeof(obj) == "object") output[z] = deepClone(obj)
else output[z] = obj;
}
return output;
}
console.log(deepClone(obj));