如何深入克隆在JavaScript中

你如何深入克隆一个Javascript对象?

我知道有基于像JSON.parse(JSON.stringify(o))$.extend(true, {}, o)框架的各种function$.extend(true, {}, o)但我不想使用这样的框架。

创build深度克隆的最优雅或有效的方法是什么?

我们关心像克隆数组这样的边缘情况。 不打破原型链,处理自我参考。

我们不关心支持DOM对象的拷贝,因为.cloneNode就是因为这个原因而存在的。

由于我主要想在node.js使用深度克隆,所以使用V5引擎的ES5function是可以接受的。

[编辑]

在任何人build议之前,让我提一下,通过原型从对象inheritance并克隆它来创build拷贝之间有明显的区别。 前者弄乱了原型链。

[进一步编辑]

看完你的回答后,我发现克隆整个物体是一个非常危险和困难的游戏,令人讨厌的发现。 以下面的基于闭包的对象为例

 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 

有什么办法可以编写一个克隆对象的克隆函数,在克隆时具有相同的状态,但不能在JS中编写一个JSparsing器来改变o的状态。

现在不再需要这样的function了。 这仅仅是学术兴趣。

这真的取决于你想克隆什么。 这是一个真正的JSON对象或JavaScript中的任何对象? 如果你想做任何克隆,它可能会让你陷入一些麻烦。 哪个麻烦? 我将在下面进行解释,但首先是克隆对象文字,任何原语,数组和节点的代码示例。

 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") { var 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"; } } ] }) 

现在,我们来讨论克隆REAL对象时可能遇到的问题。 我现在正在谈论你所做的事情

 var User = function(){} var newuser = new User(); 

当然,你可以克隆它们,这不是一个问题,每个对象都暴露了构造函数的属性,你可以用它来克隆对象,但它不会总是工作。 你也可以在这个对象上做简单for in事情,但它走向了同一个方向 – 麻烦。 我也在代码中包含了克隆function,但是它被if( false )语句排除了。

那么,为什么克隆会是一种痛苦呢? 那么首先,每个对象/实例可能都有一些状态。 你永远不能确定你的对象没有例如一个私有variables,如果是这种情况,通过克隆对象,你只是打破了状态。

想象一下,没有国家,没关系。 那我们还有另外一个问题。 通过“构造”方法克隆会给我们另一个障碍。 这是一个参数依赖。 你永远无法确定,创造这个东西的人没有做过某种事情

 new User({ bike : someBikeInstance }); 

如果是这样的话,那么你倒霉了,有些BikeInstance可能是在一些上下文中创build的,而且这个上下文对于克隆方法是未知的。

那么该怎么办? 您仍然可以for in解决scheme中做到这一点,并将这些对象视为普通的对象文字,但是可能根本不想克隆这些对象,只是传递此对象的引用?

另一个解决scheme是 – 你可以设定一个约定,所有必须克隆的对象应该自己实现这个部分,并提供适当的API方法(如cloneObject)。 cloneNode正在为DOM做些什么。

你决定。

非常简单的方法,也许太简单了:

 var cloned = JSON.parse(JSON.stringify(objectToClone)); 

因为JSON不支持undefinedfunction () {}值,所以JSON.parse(JSON.stringify())组合深入复制Javascript对象是无效的,因此JSON.stringify将忽略这些代码段将JavaScript对象“串化”(编组)成JSON。

以下函数将深度复制对象,并且不需要第三方库(jQuery,LoDash等)。

 function copy(aObject) { var bObject, v, k; bObject = Array.isArray(aObject) ? [] : {}; for (k in aObject) { v = aObject[k]; bObject[k] = (typeof v === "object") ? copy(v) : v; } return bObject; } 

Underscore.js contrib库函数有一个叫做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); 

这里是一个ES6函数,它也可以用于循环引用的对象:

 function deepClone(obj, hash = new WeakMap()) { if (Object(obj) !== obj) return obj; // primitives 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 

正如其他人已经注意到这个问题和类似的问题,从一般意义上说,克隆一个“对象”在JavaScript中是可疑的。

然而,有一类对象,我将调用“数据”对象,即从文字和/或简单的属性分配或从JSON反序列化,这是合理的要克隆的对象。 就在今天,我想人为地夸大从服务器收到的数据5 x来testing大数据集发生了什么,但对象(一个数组)和它的孩子必须是不同的对象才能正常工作。 克隆允许我这样做,以增加我的数据集:

 return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta)); 

我经常最终克隆数据对象的另一个地方是将数据提交回主机,在那里我想从数据模型中的对象剥离状态字段,然后发送它。 例如,我可能想要从对象中删除以“_”开始的所有字段,因为它被克隆。

这是我最终编写的一般代码,包括支持数组和select器来select要克隆的成员(使用“path”string来确定上下文):

 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; } 

假设一个非null根对象并且没有成员select,最简单合理的深度克隆解决scheme是:

 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; } 

我注意到Map应该需要特殊的处理,因此在这个线程中的所有build议,代码将是:

 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; } 

这是我使用的深层克隆方法,我认为它很好,希望你提出build议

 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; }