How do I correctly clone a JavaScript object?
我有一个对象,
如何正确克隆javascript对象?
对于javascript中的任何对象,这样做都不是简单或简单的。您将遇到错误地从对象原型中提取属性的问题,这些属性应该保留在原型中,而不是复制到新实例中。例如,如果您正在向
除了不可枚举的属性之外,当您试图复制具有隐藏属性的对象时,还会遇到更困难的问题。例如,
然而,寻求优雅解决方案的另一个障碍是如何正确地建立原型继承。如果源对象的原型是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function clone(obj) { if (null == obj ||"object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } var d1 = new Date(); /* Executes function after 5 seconds. */ setTimeout(function(){ var d2 = clone(d1); alert("d1 =" + d1.toString() +" d2 =" + d2.toString()); }, 5000); |
当我不得不进行一般性的深度复制时,我最终做出了妥协,假设我只需要复制一个普通的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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."); } |
只要对象和数组中的数据形成一个树结构,上面的函数就可以适用于我提到的6个简单类型。也就是说,对象中没有对同一数据的多个引用。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // This would be cloneable: var tree = { "left" : {"left" : null,"right" : null,"data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : {"left" : null,"right" : null,"data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cyclicGraph = { "left" : {"left" : null,"right" : null,"data" : 3 }, "data" : 8 }; cyclicGraph["right"] = cyclicGraph; |
它将无法处理任何javascript对象,但对于许多目的来说,只要您不认为它只适用于您向其抛出的任何东西,它就足够了。
如果您不在对象中使用函数,一个非常简单的一行程序可以是:
1 | var cloneOfA = JSON.parse(JSON.stringify(a)); |
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参阅本文中有关浏览器的结构化克隆算法的内容,该算法在向工作者或从工作者发送消息时使用。它还包含一个深度克隆功能。
使用jquery,可以使用extend进行浅复制:
1 | var copiedObject = jQuery.extend({}, originalObject) |
对copiedObject的后续更改不会影响原始对象,反之亦然。
或制作深度复制:
1 | var copiedObject = jQuery.extend(true, {}, originalObject) |
在ECMAScript 6中有object.assign方法,它将所有可枚举的自身属性的值从一个对象复制到另一个对象。例如:
1 2 | var x = {myProp:"value"}; var y = Object.assign({}, x); |
但请注意,嵌套对象仍然作为引用复制。
每分钟
- 如果你想要浅拷贝,使用
Object.assign({}, a) 。 - 对于"深度"拷贝,使用
JSON.parse(JSON.stringify(a)) 。
不需要外部库,但需要首先检查浏览器兼容性。
有许多答案,但没有一个提到object.create从ecmascript 5,这无疑不会给你一个准确的副本,但将源设置为新对象的原型。
因此,这不是问题的确切答案,但它是一个单行解决方案,因此很优雅。它最适用于两种情况:
例子:
1 2 3 4 5 6 7 8 | var foo = { a : 1 }; var bar = Object.create(foo); foo.a; // 1 bar.a; // 1 foo.a = 2; bar.a; // 2 - prototype changed bar.a = 3; foo.a; // Still 2, since setting bar.a makes it an"own" property |
为什么我认为这个解决方案更优越?它是本地的,因此没有循环,没有递归。但是,旧的浏览器将需要一个polyfill。
一种在一行代码中克隆javascript对象的优雅方法
1 | 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.
多读…
支持旧浏览器的polyfill:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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; } }); } |
如果您对一个浅拷贝还满意,那么underline.js库有一个clone方法。
1 | y = _.clone(x); |
或者你可以像
1 | copiedObject = _.extend({},originalObject); |
互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,包括为什么接受的答案不应该被接受。
启动情况我想深入复制一个javascript
那么让我们先创建一个
1 2 3 4 5 6 7 | function Circ() { this.me = this; } function Nested(y) { this.y = y; } |
让我们把所有东西放在一个叫
1 2 3 4 5 | var a = { x: 'a', circ: new Circ(), nested: new Nested('a') }; |
接下来,我们要将
1 2 3 4 | var b = a; b.x = 'b'; b.nested.y = 'b'; |
你知道这里发生了什么,因为如果不是的话,你甚至不会在这个伟大的问题上着陆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | console.log(a, b); a --> Object { x:"b", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } b --> Object { x:"b", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } |
现在让我们找到一个解决方案。
杰森我尝试的第一次尝试是使用
1 2 3 4 | var b = JSON.parse( JSON.stringify( a ) ); b.x = 'b'; b.nested.y = 'b'; |
别在上面浪费太多时间,你会得到
让我们看一下公认的答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | function cloneSO(obj) { // Handle the 3 simple types, and null or undefined if (null == obj ||"object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = cloneSO(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } |
看起来不错,哈?它是对象的递归副本,并处理其他类型,如
1 2 3 4 | var b = cloneSO(a); b.x = 'b'; b.nested.y = 'b'; |
递归和
在和我的同事争论之后,我的老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫
1 2 3 4 | var b = Object.create(a); b.x = 'b'; b.nested.y = 'b'; |
这个解决方案是在一段时间前添加到javascript中的,甚至可以处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | console.log(a, b); a --> Object { x:"a", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } b --> Object { x:"b", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } |
…你看,它不适用于内部的嵌套结构。
本地解决方案的polyfill与IE8一样,旧版浏览器中的
1 2 3 4 5 6 7 8 9 10 | function F() {}; function clonePF(o) { F.prototype = o; return new F(); } var b = clonePF(a); b.x = 'b'; b.nested.y = 'b'; |
我把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | console.log(a, b); a --> Object { x:"a", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } b --> F { x:"b", circ: Circ { me: Circ { ... } }, nested: Nested { y:"b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> true |
与本机解决方案的问题相同,但输出稍差。
更好(但不完美)的解决方案在深入研究时,我发现了一个类似的问题(在javascript中,在执行深度复制时,如何避免一个循环,因为属性是"this"?)但有更好的解决办法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function cloneDR(o) { const gdcc ="__getDeepCircularCopy__"; if (o !== Object(o)) { return o; // primitive value } var set = gdcc in o, cache = o[gdcc], result; if (set && typeof cache =="function") { return cache(); } // else o[gdcc] = function() { return result; }; // overwrite if (o instanceof Array) { result = []; for (var i=0; i<o.length; i++) { result[i] = cloneDR(o[i]); } } else { result = {}; for (var prop in o) if (prop != gdcc) result[prop] = cloneDR(o[prop]); else if (set) result[prop] = cloneDR(cache); } if (set) { o[gdcc] = cache; // reset } else { delete o[gdcc]; // unset again } return result; } var b = cloneDR(a); b.x = 'b'; b.nested.y = 'b'; |
让我们来看看输出…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | console.log(a, b); a --> Object { x:"a", circ: Object { me: Object { ... } }, nested: Object { y:"a" } } b --> Object { x:"b", circ: Object { me: Object { ... } }, nested: Object { y:"b" } } console.log(typeof a, typeof b); a --> object b --> object console.log(a instanceof Object, b instanceof Object); a --> true b --> true console.log(a instanceof F, b instanceof F); a --> false b --> false |
需求是匹配的,但还有一些小问题,包括将
The structure of trees that share a leaf won't be copied, they will become two independent leaves:
1 2 3 4 5 6 7 8 9 | [Object] [Object] / \ / \ / \ / \ |/_ _\| |/_ _\| [Object] [Object] ===> [Object] [Object] \ / | | \ / | | _\| |/_ \|/ \|/ [Object] [Object] [Object] |
结论
最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真正深度副本。它处理简单的
杰西德
一个特别不好的解决方案是使用JSON编码对没有成员方法的对象进行深度复制。方法是JSON对目标对象进行编码,然后通过对其进行解码,得到所需的副本。您可以根据需要多次解码以制作尽可能多的副本。
当然,函数不属于JSON,所以这只适用于没有成员方法的对象。
这种方法非常适合我的用例,因为我将JSON blob存储在一个键值存储中,当它们在一个Javascript API中作为对象公开时,每个对象实际上包含一个对象原始状态的副本,这样我们就可以在调用者改变公开对象后计算delta。
1 2 3 4 5 6 7 8 | var object1 = {key:"value"}; var object2 = object1; object2 = JSON.stringify(object1); object2 = JSON.parse(object2); object2.key ="a change"; console.log(object1);// returns value |
好的,假设您下面有这个对象,并且您想克隆它:
1 | let obj = {a:1, b:2, c:3}; //ES6 |
或
1 | var obj = {a:1, b:2, c:3}; //ES5 |
答案主要取决于您使用的ecmascript,在
1 | let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3}; |
或者使用如下扩散运算符:
1 | let cloned = {...obj}; //new {a:1, b:2, c:3}; |
但是,如果您使用
1 2 | let cloned = JSON.parse(JSON.stringify(obj)); //new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over |
您可以简单地使用Spread属性复制一个没有引用的对象。但要小心(请参见注释),"copy"仅位于最低的对象/数组级别。嵌套属性仍然是引用!
完全克隆:
1 2 3 4 5 6 7 | let x = {a: 'value1'} let x2 = {...x} // => mutate without references: x2.a = 'value2' console.log(x.a) // => 'value1' |
使用第二级引用克隆:
1 2 3 4 5 6 7 | const y = {a: {b: 'value3'}} const y2 = {...y} // => nested object is still a references: y2.a.b = 'value4' console.log(y.a.b) // => 'value4' |
实际上,javascript不支持本机的深度克隆。使用实用功能。例如Ramda:
http://ramdajs.com/docs/#clone
对于那些使用AngularJS的用户,还可以直接克隆或扩展这个库中的对象。
1 | var destination = angular.copy(source); |
或
1 | angular.copy(source, destination); |
更多内容请参阅angular.copy文档…
列维的回答几乎是完整的,这是我的一点贡献:有一种方法可以处理递归引用,请看这一行
如果对象是xml dom元素,则必须改用clonenode
受A.Levy详尽研究和Calvin原型方法的启发,我提供了以下解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Object.prototype.clone = function() { if(this.cloneNode) return this.cloneNode(true); var copy = this instanceof Array ? [] : {}; for(var attr in this) { if(typeof this[attr] =="function" || this[attr]==null || !this[attr].clone) copy[attr] = this[attr]; else if(this[attr]==this) copy[attr] = copy; else copy[attr] = this[attr].clone(); } return copy; } Date.prototype.clone = function() { var copy = new Date(); copy.setTime(this.getTime()); return copy; } Number.prototype.clone = Boolean.prototype.clone = String.prototype.clone = function() { return this; } |
另请参见答案中安迪·伯克的笔记。
这是一个你可以使用的函数。
1 2 3 4 5 6 7 8 | function clone(obj) { if(obj == null || typeof(obj) != 'object') return obj; var temp = new obj.constructor(); for(var key in obj) temp[key] = clone(obj[key]); return temp; } |
在ES-6中,只需使用object.assign(…)。前任:
1 2 | let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); |
这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/
本文简介:Brian Huisman如何在javascript中复制数组和对象:
1 2 3 4 5 6 7 8 9 | Object.prototype.clone = function() { var newObj = (this instanceof Array) ? [] : {}; for (var i in this) { if (i == 'clone') continue; if (this[i] && typeof this[i] =="object") { newObj[i] = this[i].clone(); } else newObj[i] = this[i] } return newObj; }; |
在EcmaScript 2018中
1 | let objClone = { ...obj }; |
请注意,嵌套对象仍作为引用复制。
您可以克隆一个对象,并使用一行代码从上一个对象中删除任何引用。简单D
1 2 | var obj1=text:'moo1'var obj2=object.create(obj1);//创建一个没有引用的新克隆 n obj2.text='moo2';//仅正确更新obj2的文本 n console.log(obj1,obj2);//输出:obj1:文本:'moo1',obj2:文本:'moo2';< /代码> <<P>>对于当前不支持对象的浏览器/引擎。创建可以使用此polyfil</p n[cc lang="javascript"]//polyfill object.create if it not exis如果(!)对象.创建)object.create=函数(o)var f=函数()F.原型=O返回新的f-()}< /代码> </prn<div class="suo-content">[collapse title=""]<ul><li>+1埃多克斯1〔8〕显然是要走的路。</li><li>完美答案。也许你可以为<wyn>Object.hasOwnProperty</wyn>增加一个解释?这样人们就知道如何阻止搜索原型链接。</li><li>很好,但是polyfill在哪些浏览器中工作?</li><li>如果原始obj1中存在对象或数组,并且它们更改了新obj2中的对象或数组,则会修改obj1的副本,因为它们是通过引用存储的。示例:`var obj1=thing:var obj2=object.create(obj1);obj2.thing.innerthing='test';console.log(obj1)`输出为obj1和obj2现在是<wyn>{ thing: { innerThing:"test" } }</wyn>。</li><li>这是用obj1作为原型创建obj2。它只起作用,因为您正在隐藏obj2中的<wyn>text</wyn>成员。您没有制作副本,只是在obj2上找不到成员时延迟原型链。</li><li>@五六度设置<wyn>obj2</wyn>的属性在<wyn>obj2</wyn>上创建"自有"属性,不修改<wyn>obj1</wyn>的属性。</li><li>这并不是"没有引用"创建它,而是将引用移动到原型。这仍然是一个参考。如果原始属性发生更改,"克隆"中的原型属性也会发生更改。它根本不是克隆人。</li></ul>[/collapse]</div><hr><P>一个旧问题的新答案!如果您喜欢将EcmaScript 2016(ES6)与Spread语法结合使用,那么很容易。</P>[cc lang="javascript"]keepMeTheSame = {first:"Me!", second:"You!"}; cloned = {...keepMeTheSame} |
这为对象的浅副本提供了一种干净的方法。制作一个深度复制,也就是说对每一个递归嵌套对象中的每个值创建一个新的复制,需要上面更重的解决方案。
JavaScript不断发展。
1 | let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj) |
ES6解决方案,如果您想(Shallow)克隆一个类实例,而不仅仅是一个属性对象。
使用LADAS:
1 | var y = _.clone(x, true); |
对克隆简单对象感兴趣:
json.parse(json.stringify(json_original));
来源:如何将javascript对象复制到新变量而不是通过引用?
我认为有一个简单而有效的答案。在深度复制中,有两个问题:
所以我认为一个简单的解决方案是首先序列化和反序列化,然后对其进行赋值以复制函数。
1 2 3 | let deepCloned = JSON.parse(JSON.stringify(source)); let merged = Object.assign({}, source); Object.assign(merged, deepCloned); |
尽管这个问题有很多答案,但我希望这个问题也能有所帮助。
Jan Turo_上面的答案非常接近,由于兼容性问题,可能是最好在浏览器中使用的答案,但它可能会导致一些奇怪的枚举问题。例如,执行<;。/<;pre><;cod>;for(somearray中的var i)…}<;/code><;/pr<;>;将在遍历数组元素后将clone()方法分配给i。下面是一个避免枚举并与node.js<;一起使用的改编版本。/<;pre><;cod>;object.defineproperty(object.prototype""cl"",)值:函数()。如果(这个,克隆诺德)n返回这个。克隆诺德(真的 n n var copy=这个数组实例?[]:{N代表(此中的var attrn if(typeof this[attr]""funct""this[attr]==空!此[属性]。克隆n copy[attr]=这个[attr]n否则,如果(this[attr]=thisn copy[attr]=通用作战图N标高n copy[attr]=此[attr].克隆(nn返回copn n对象.defineproperty(date.prototyp""cl"",)值:函数()。 var copy=新日期( copy.settime(this.gettime())返回copn n对象.defineproperty(number.prototyp""cl"",value:function()返回此;object.defineproperty(boolean.prototyp""cl"",value:function()返回此;object.defineproperty(string.prototype""cl"",value:function()返回此;);<;/code><;/pr<;>;这避免了使clone()方法可枚举,因为defineproperty()默认可枚举为false<;。/\
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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; }; |
我已经编写了自己的实现。不确定它是否算作更好的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* a function for deep cloning objects that contains other nested objects and circular structures. objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object. index (z) | | | | | | depth (x) |_ _ _ _ _ _ _ _ _ _ _ _ /_/_/_/_/_/_/_/_/_/ /_/_/_/_/_/_/_/_/_/ /_/_/_/_/_/_/...../ /................./ /..... / / / /------------------ object length (y) / */ |
具体实施如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | function deepClone(obj) { var depth = -1; var arr = []; return clone(obj, arr, depth); } /** * * @param obj source object * @param arr 3D array to store the references to objects * @param depth depth of the current object relative to the passed 'obj' * @returns {*} */ function clone(obj, arr, depth){ if (typeof obj !=="object") { return obj; } var length = Object.keys(obj).length; // native method to get the number of properties in 'obj' var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object if(result instanceof Array){ result.length = length; } depth++; // depth is increased because we entered an object here arr[depth] = []; // this is the x-axis, each index here is the depth arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props) // start the depth at current and go down, cyclic structures won't form on depths more than the current one for(var x = depth; x >= 0; x--){ // loop only if the array at this depth and length already have elements if(arr[x][length]){ for(var index = 0; index < arr[x][length].length; index++){ if(obj === arr[x][length][index]){ return obj; } } } } arr[depth][length].push(obj); // store the object in the array at the current depth and length for (var prop in obj) { if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth); } return result; } |
我只是想在这篇文章的所有
在火狐中
1 2 3 | var a = {"test":"test"}; var b = Object.create(a); console.log(b);′ |
是
在NoDjs中
1 | {} |
这是对a.levy代码的一种改编,用于处理函数和多个/循环引用的克隆-这意味着,如果克隆的树中的两个属性是同一对象的引用,则克隆的对象树将使这些属性指向被引用对象的一个和同一个克隆。这也解决了循环依赖的情况,如果不处理循环依赖,将导致无限循环。算法的复杂度为O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | function clone(obj){ var clonedObjectsArray = []; var originalObjectsArray = []; //used to remove the unique ids when finished var next_objid = 0; function objectId(obj) { if (obj == null) return null; if (obj.__obj_id == undefined){ obj.__obj_id = next_objid++; originalObjectsArray[obj.__obj_id] = obj; } return obj.__obj_id; } function cloneRecursive(obj) { if (null == obj || typeof obj =="string" || typeof obj =="number" || typeof obj =="boolean") return obj; // Handle Date if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { var copy = []; for (var i = 0; i < obj.length; ++i) { copy[i] = cloneRecursive(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { if (clonedObjectsArray[objectId(obj)] != undefined) return clonedObjectsArray[objectId(obj)]; var copy; if (obj instanceof Function)//Handle Function copy = function(){return obj.apply(this, arguments);}; else copy = {}; clonedObjectsArray[objectId(obj)] = copy; for (var attr in obj) if (attr !="__obj_id" && obj.hasOwnProperty(attr)) copy[attr] = cloneRecursive(obj[attr]); return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); } var cloneObj = cloneRecursive(obj); //remove the unique ids for (var i = 0; i < originalObjectsArray.length; i++) { delete originalObjectsArray[i].__obj_id; }; return cloneObj; } |
一些快速测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var auxobj = { prop1 :"prop1 aux val", prop2 : ["prop2 item1","prop2 item2"] }; var obj = new Object(); obj.prop1 ="prop1_value"; obj.prop2 = [auxobj, auxobj,"some extra val", undefined]; obj.nr = 3465; obj.bool = true; obj.f1 = function (){ this.prop1 ="prop1 val changed by f1"; }; objclone = clone(obj); //some tests i've made console.log("test number, boolean and string cloning:" + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool)); objclone.f1(); console.log("test function cloning 1:" + (objclone.prop1 == 'prop1 val changed by f1')); objclone.f1.prop = 'some prop'; console.log("test function cloning 2:" + (obj.f1.prop == undefined)); objclone.prop2[0].prop1 ="prop1 aux val NEW"; console.log("test multiple references cloning 1:" + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1)); console.log("test multiple references cloning 2:" + (objclone.prop2[1].prop1 != obj.prop2[0].prop1)); |
由于Mindeavor声明要克隆的对象是一个"文本构造"的对象,解决方案可能是简单地多次生成该对象,而不是克隆该对象的一个实例:
1 2 3 4 5 6 7 8 9 10 11 | function createMyObject() { var myObject = { ... }; return myObject; } var myObjectInstance1 = createMyObject(); var myObjectInstance2 = createMyObject(); |
使用来自NPM的deepcopy。在浏览器和节点中作为NPM模块工作…
网址:https://www.npmjs.com/package/deepcopy
设a=deepcopy(b)
下面是我的深度克隆版本,包括函数和循环引用的处理。
https://github.com/radsimu/uaicnlptoolkit/blob/master/modules/ggs/ggs engine/src/main/resources/ro/uaic/info/nlptools/ggs/engine/core/jsinitcode.js_l17
请参阅http://www.w3.org/html/wg/drafts/html/master/infrastructure.html安全传递结构化数据,了解W3C的"安全传递结构化数据"算法,该算法由浏览器实现,用于将数据传递给eg web工作者。但是,它有一些限制,因为它不处理函数。有关更多信息,请参阅https://developer.mozilla.org/en-us/docs/dom/the_-structured_-clone_算法,包括JS中的一个替代算法,它可以帮助您实现目标。
根据Airbnb javascript样式指南,404个参与者:
Prefer the object spread operator over Object.assign to shallow-copy
objects. Use the object rest operator to get a new object with certain
properties omitted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // this mutates `original` ?_? delete copy.a; // so does this // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 } |
另外,我想提醒你,尽管Airbnb几乎不建议使用"物体扩散"操作符。请记住,Microsoft Edge仍然不支持此2018功能。
ES2016+兼容表>>
您可以使用函数闭包获得深度复制的所有好处,而不需要深度复制。这是一个非常不同的范例,但效果很好。不要试图复制现有对象,只需在需要时使用函数来实例化一个新对象。
首先,创建一个返回对象的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | function template() { return { values: [1, 2, 3], nest: {x: {a:"a", b:"b <div class="suo-content">[collapse title=""]<ul><li>直到javascript 1.8.5才实现object.keys,这意味着它在IE 8和其他传统浏览器中不可用。因此,虽然这个答案在现代浏览器中很有用,但在IE8中会失败。因此,如果使用此方法,则必须正确地模拟object.keys polyfill。</li></ul>[/collapse]</div><hr><P>基于"模板"克隆对象。如果您不想要一个精确的副本,但是您想要某种可靠的克隆操作的健壮性,但是您只想要克隆位,或者您想要确保可以控制克隆的每个属性值的存在或格式,那么您该怎么办?</P><P>我贡献这个是因为它对我们有用,我们创造它是因为我们找不到类似的东西。您可以使用它来克隆一个基于"模板"对象的对象,该对象指定了我要克隆的对象的哪些属性,并且模板允许函数将这些属性转换为其他属性,如果它们不存在于源对象上或者您想处理克隆的话。如果没有用,我相信有人可以删除这个答案。</P>[cc lang="javascript"] function isFunction(functionToCheck) { var getType = {}; return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; } function cloneObjectByTemplate(obj, tpl, cloneConstructor) { if (typeof cloneConstructor ==="undefined") { cloneConstructor = false; } if (obj == null || typeof (obj) != 'object') return obj; //if we have an array, work through it's contents and apply the template to each item... if (Array.isArray(obj)) { var ret = []; for (var i = 0; i < obj.length; i++) { ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor)); } return ret; } //otherwise we have an object... //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values. var temp = cloneConstructor ? new obj.constructor() : {}; for (var key in tpl) { //if we are provided with a function to determine the value of this property, call it... if (isFunction(tpl[key])) { temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value } else { //if our object has this property... if (obj[key] != undefined) { if (Array.isArray(obj[key])) { temp[key] = []; for (var i = 0; i < obj[key].length; i++) { temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor)); } } else { temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor); } } } } return temp; } |
一个简单的方法是这样称呼它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var source = { a:"whatever", b: { x:"yeah", y:"haha" } }; var template = { a: true, //we want to clone"a" b: { x: true //we want to clone"b.x" too } }; var destination = cloneObjectByTemplate(source, template); |
如果您想使用一个函数来确保返回一个属性,或者确保它是一个特定的类型,请使用这样的模板。我们没有使用id:true而是提供了一个函数,它仍然只是复制源对象的id属性,但它确保它是一个数字,即使它在源对象上不存在。
1 2 3 4 5 6 | var template = { ID: function (srcObj) { if(srcObj.ID == undefined){ return -1; } return parseInt(srcObj.ID.toString()); } } |
数组可以很好地克隆,但如果您愿意,您也可以让自己的函数处理这些单独的属性,并执行如下特殊操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var template = { tags: function (srcObj) { var tags = []; if (process.tags != undefined) { for (var i = 0; i < process.tags.length; i++) { tags.push(cloneObjectByTemplate( srcObj.tags[i], { a : true, b : true } //another template for each item in the array ); } } return tags; } } |
因此,在上面,我们的模板只复制源对象的"tag s"属性(如果它存在)(假设它是一个数组),对于该数组中的每个元素,调用clone函数根据第二个模板单独克隆它,该模板只复制每个标记元素的"a"和"b"属性。
如果要将对象移入或移出节点,并且希望控制克隆这些对象的哪些属性,那么这是在node.js中控制这些属性的一种很好的方法,代码也可以在浏览器中工作。
下面是它的使用示例:http://jsfiddle.net/hjchlt1/
我在标量对象的情况下尝试过这种方法,它适用于我:
1 2 3 4 5 6 7 8 9 10 11 | function binder(i) { return function () { return i; }; } a=1; b=binder(a)(); // copy value of a into b alert(++a); // 2 alert(b); // still 1 |
当做。
(以下主要是@a.levy,@jan turo的集成?;/>;,<;a href"https://stackoverflow.com/a/375503916>;红色<;/>;'s答案,<;a href"https://stackoverflow.com/questions/728360/how-do-i-corcorcorcorrec正确-clone-a-javascript-object/53737490 35; comment96229185>;levirobert;/>;,<,<,<;a href"https://stackoverflow.com/q/728360 35; comment42972840>;rob;/>;'s的评论,非常感谢!一个<;>n<;p><;stron>;deep cop<;/stron>;?-是的!(主要是<;b>;<;stron>;shallow cop<;/stron>;?-不!(除了<;cod>;prox<;/cod>;<;/>n<;h>;函数<;/h>n<;pre><;cod>;函数克隆(对象){/n按值而不是参照深度复制"object"n除了:`proxy`*如果对象!==对象(对象))返回对象/n检查对象是否属于基元数据类型*n if(object instanceof node)返回object.clone node(true)//克隆dom树n让对象//对象的克隆n开关(object.constructor)n大小写对象n _对象=CloneObject(对象布雷亚N盒阵列n _object=object.map(v&;gt;array.isarray(v)?克隆(V):V布雷亚n个案件日期n _对象=新日期(+对象布雷亚n case函数n _object=new函数"return"+字符串(object))。(n object.defineproperties(_object,object.getOwnPropertyDescriptors(object)布雷亚n默认值n开关(object.prototype.toString.call(object.constructor))。N////词干自n case"[对象函数"://`类n case"[对象未定义]:/`object.create(空)n _对象=CloneObject(对象布雷亚n默认值://`proxyn _object=objec布雷亚nnn函数cloneObject(对象)n const _object=object.create(object.getprototypeof(object))。/n为继承分配[[原型]]*n reflect.ownkeys(object).foreach(key&;gt;object[key]==对象/n检查是否处理无限递归引用(循环结构)*n?_ object[key]=撊objecn:_object[key]=克隆(object[key]nn返回nn返回n<;/code><;/pr>n<;h>;//测试<;/h>n<;pre><;cod>;obj0=未定义的NUL:空NT:真n n:9N STR1:"斯特林"N STR2:n符号:符号"symbo")n[符号"):数学.en函数(参数)No:n n:0No:n f:函数(…args){nn}N arr:[0,[1,2]]新日期(ND)nnobj1=克隆(obj0NOBJ1.O.N=nobj1.o.o.g=功能G(A=0,B=0)返回A+BNobj1.arr[1][1]号=nobj1.d.settime(+obj0.d+60*1000)nconsole.log",重复(2**6)nconsole.log(&;gt;&;gt;:测试-常规"nconsole.log"obj0:"0",Obj0nconsole.log"obj1:Obj1,Obj1nconsole.log"obj0未受到干扰:n",json.stringify(obj0)nconsole.log"obj1:n",json.字符串化(obj1)nconsole.log",重复(2**6)nconsole.log(&;gt;&;gt;:test-无限递归"nobj0.o.recursion=obj0。nobj1=克隆(obj0nconsole.log"obj0:"0",Obj0nconsole.log"obj1:Obj1,Obj1nconsole.log",重复(2**6)nconsole.log(&;gt;&;gt;:测试-Classe"n类人n构造函数(名称)n this.name=南nnNClass男孩延伸人{nboy.prototype.sex="NBY0=新博nboy0.hobby=运动:"滑翔"nboy1=克隆(boy0nboy1.hobby.sport="spacefligh"nboy0.name="ne"nboy1.name="on"nconsole.log"boy0:"0"nconsole.log"boy1:n",boy1)<;/code><;/pr>n<;h>;参考<;/h>n<;o>;<;li><;a a href"https://md n.io/objec/creat"rel"rel"nofollow noreferre"><;cod>;objec.creat(<;/cod>; md<;/a><;/l>;<;<;li><;a href"https://md n.io/objec/definepropete"rel"nofollow noreferre"><;cod>;objec.define属性(<;/cod>; \124;/a><;/a>;/a><;;;/l>;/l>;/l>;<"https://developer.mozilla.org/en-us/docs/web/javascript/Enumerability_a属性的ndu所有权检测表"rel"nofollow noreferr>;属性的可枚举性和所有权md<;/a><;/l>;<;/o>诺克。
结构化克隆
HTML标准包括一个内部结构化的克隆/序列化算法,可以创建对象的深度克隆。它仍然局限于某些内置类型,但是除了JSON支持的少数类型之外,它还支持日期、regexp、映射、集合、blob、文件列表、imagedatas、稀疏数组、类型数组,以及将来可能更多的类型。它还保留克隆数据中的引用,允许它支持可能导致JSON错误的循环和递归结构。
node.js中的支持:实验??node.js中的
1 2 3 4 5 | const v8 = require('v8'); const structuredClone = obj => { return v8.deserialize(v8.serialize(obj)); }; |
浏览器中的直接支持:也许最终????
浏览器目前不提供结构化克隆算法的直接接口,但在Github上的whatwg/html_793中讨论了全局
1 | const clone = structuredClone(original); |
除非已发布,否则浏览器的结构化克隆实现只能间接公开。
异步解决方法:可用。???使用现有API创建结构化克隆的开销较低的方法是通过消息通道的一个端口发布数据。另一个端口将发出一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); |
实例使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const main = async () => { const original = { date: new Date(), number: Math.random() }; original.self = original; const clone = await structuredCloneAsync(original); // They're different objects: console.assert(original !== clone); console.assert(original.date !== clone.date); // They're cyclical: console.assert(original.self === original); console.assert(clone.self === clone); // They contain equivalent values: console.assert(original.number === clone.number); console.assert(Number(original.date) === Number(clone.date)); console.log("Assertions complete."); }; main(); |
同步解决方法:糟糕!???
同步创建结构化克隆没有好的选择。这里有一些不切实际的黑客。
1 2 3 4 5 6 7 | const structuredClone = obj => { const oldState = history.state; history.replaceState(obj, null); const clonedObj = history.state; history.replaceState(oldState, null); return clonedObj; }; |
实例使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 'use strict'; const main = () => { const original = { date: new Date(), number: Math.random() }; original.self = original; const clone = structuredClone(original); // They're different objects: console.assert(original !== clone); console.assert(original.date !== clone.date); // They're cyclical: console.assert(original.self === original); console.assert(clone.self === clone); // They contain equivalent values: console.assert(original.number === clone.number); console.assert(Number(original.date) === Number(clone.date)); console.log("Assertions complete."); }; const structuredClone = obj => { const oldState = history.state; history.replaceState(obj, null); const clonedObj = history.state; history.replaceState(oldState, null); return clonedObj; }; main(); |
虽然是同步的,但速度可能非常慢。它会产生与操作浏览器历史记录相关的所有开销。重复调用此方法会导致chrome暂时失去响应。
1 2 3 4 5 | const structuredClone = obj => { const n = new Notification('', {data: obj, silent: true}); n.onshow = n.close.bind(n); return n.data; }; |
实例使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 'use strict'; const main = () => { const original = { date: new Date(), number: Math.random() }; original.self = original; const clone = structuredClone(original); // They're different objects: console.assert(original !== clone); console.assert(original.date !== clone.date); // They're cyclical: console.assert(original.self === original); console.assert(clone.self === clone); // They contain equivalent values: console.assert(original.number === clone.number); console.assert(Number(original.date) === Number(clone.date)); console.log("Assertions complete."); }; const structuredClone = obj => { const n = new Notification('', {data: obj, silent: true}); n.close(); return n.data; }; main(); |
如果您的对象中没有循环依赖项,我建议使用其他答案之一或jquery的复制方法,因为它们看起来都非常有效。
如果存在循环依赖关系(即两个子对象相互链接),那么您就有点被拧紧了,因为(从理论角度)没有办法优雅地解决这个问题。
我认为,在没有库的情况下,使用缓存进行循环是最好的方法。
而被低估的weakmap带来了循环的问题,其中存储对新老对象的引用可以帮助我们很容易地重新创建整个树。
我阻止了对DOM元素的深度克隆,可能您不想克隆整个页面:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | function deepCopy(object) { const cache = new WeakMap(); // Map of old - new references function copy(obj) { if (typeof obj !== 'object' || obj === null || obj instanceof HTMLElement ) return obj; // primitive value or HTMLElement if (obj instanceof Date) return new Date().setTime(obj.getTime()); if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags); if (cache.has(obj)) return cache.get(obj); const result = obj instanceof Array ? [] : {}; cache.set(obj, result); // store reference to object before the recursive starts if (obj instanceof Array) { for(const o of obj) { result.push(copy(o)); } return result; } const keys = Object.keys(obj); for (const key of keys) result[key] = copy(obj[key]); return result; } return copy(object); } |
一些测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // #1 const obj1 = { }; const obj2 = { }; obj1.obj2 = obj2; obj2.obj1 = obj1; // Trivial circular reference var copy = deepCopy(obj1); copy == obj1 // false copy.obj2 === obj1.obj2 // false copy.obj2.obj1.obj2 // and so on - no error (correctly cloned). // #2 const obj = { x: 0 } const clone = deepCopy({ a: obj, b: obj }); clone.a == clone.b // true // #3 const arr = []; arr[0] = arr; // A little bit weird but who cares clone = deepCopy(arr) clone == arr // false; clone[0][0][0][0] == clone // true; |
注意:我使用常量,for-of循环,=>操作符和weakmaps来创建更重要的代码。今天的浏览器支持这种语法(ES6)
这里有一个现代的解决方案,它没有
1 2 3 4 5 6 7 8 | const cloneObj = (obj) => { return Object.keys(obj).reduce((dolly, key) => { dolly[key] = (obj[key].constructor === Object) ? cloneObj(obj[key]) : obj[key]; return dolly; }, {}); }; |
为了更好地理解对象的复制,这个说明性的JSbin可能很有价值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | class base { get under(){return true} } class a extends base {} const b = { get b1(){return true}, b: true } console.log('Object assign') let t1 = Object.create(b) t1.x = true const c = Object.assign(t1, new a()) console.log(c.b1 ? 'prop value copied': 'prop value gone') console.log(c.x ? 'assigned value copied': 'assigned value gone') console.log(c.under ? 'inheritance ok': 'inheritance gone') console.log(c.b1 ? 'get value unchanged' : 'get value lost') c.b1 = false console.log(c.b1? 'get unchanged' : 'get lost') console.log('-----------------------------------') console.log('Object assign - order swopped') t1 = Object.create(b) t1.x = true const d = Object.assign(new a(), t1) console.log(d.b1 ? 'prop value copied': 'prop value gone') console.log(d.x ? 'assigned value copied': 'assigned value gone') console.log(d.under ? 'inheritance n/a': 'inheritance gone') console.log(d.b1 ? 'get value copied' : 'get value lost') d.b1 = false console.log(d.b1? 'get copied' : 'get lost') console.log('-----------------------------------') console.log('Spread operator') t1 = Object.create(b) t2 = new a() t1.x = true const e = { ...t1, ...t2 } console.log(e.b1 ? 'prop value copied': 'prop value gone') console.log(e.x ? 'assigned value copied': 'assigned value gone') console.log(e.under ? 'inheritance ok': 'inheritance gone') console.log(e.b1 ? 'get value copied' : 'get value lost') e.b1 = false console.log(e.b1? 'get copied' : 'get lost') console.log('-----------------------------------') console.log('Spread operator on getPrototypeOf') t1 = Object.create(b) t2 = new a() t1.x = true const e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) } console.log(e1.b1 ? 'prop value copied': 'prop value gone') console.log(e1.x ? 'assigned value copied': 'assigned value gone') console.log(e1.under ? 'inheritance ok': 'inheritance gone') console.log(e1.b1 ? 'get value copied' : 'get value lost') e1.b1 = false console.log(e1.b1? 'get copied' : 'get lost') console.log('-----------------------------------') console.log('keys, defineProperty, getOwnPropertyDescriptor') f = Object.create(b) t2 = new a() f.x = 'a' Object.keys(t2).forEach(key=> { Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key)) }) console.log(f.b1 ? 'prop value copied': 'prop value gone') console.log(f.x ? 'assigned value copied': 'assigned value gone') console.log(f.under ? 'inheritance ok': 'inheritance gone') console.log(f.b1 ? 'get value copied' : 'get value lost') f.b1 = false console.log(f.b1? 'get copied' : 'get lost') console.log('-----------------------------------') console.log('defineProperties, getOwnPropertyDescriptors') let g = Object.create(b) t2 = new a() g.x = 'a' Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2)) console.log(g.b1 ? 'prop value copied': 'prop value gone') console.log(g.x ? 'assigned value copied': 'assigned value gone') console.log(g.under ? 'inheritance ok': 'inheritance gone') console.log(g.b1 ? 'get value copied' : 'get value lost') g.b1 = false console.log(g.b1? 'get copied' : 'get lost') console.log('-----------------------------------') |
好的,所以这可能是浅复制的最佳选择。if使用assign遵循许多示例,但它还保留继承和原型。它也很简单,适用于大多数类似数组和对象,除了那些具有构造函数要求或只读属性的对象。但这意味着它在typedarrays、regexp、日期、映射、集合和原语的对象版本(布尔值、字符串等)上失败了。
1 | function copy ( a ) { return Object.assign( new a.constructor, a ) } |
其中
您也可以将它应用于原语以获得奇怪的结果,但是…除非它最终成为一个有用的黑客,谁在乎呢。
基本内置对象和数组的结果…
1 2 3 4 5 6 7 8 | > a = { a: 'A', b: 'B', c: 'C', d: 'D' } { a: 'A', b: 'B', c: 'C', d: 'D' } > b = copy( a ) { a: 'A', b: 'B', c: 'C', d: 'D' } > a = [1,2,3,4] [ 1, 2, 3, 4 ] > b = copy( a ) [ 1, 2, 3, 4 ] |
并且由于平均get/setter、构造函数所需的参数或只读属性而失败,并且违背了父级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | > a = /\w+/g /\w+/g > b = copy( a ) // fails because source and flags are read-only /(?:)/ > a = new Date ( '1/1/2001' ) 2000-12-31T16:00:00.000Z > b = copy( a ) // fails because Date using methods to get and set things 2017-02-04T14:44:13.990Z > a = new Boolean( true ) [Boolean: true] > b = copy( a ) // fails because of of sins against the father [Boolean: false] > a = new Number( 37 ) [Number: 37] > b = copy( a ) // fails because of of sins against the father [Number: 0] > a = new String( 'four score and seven years ago our four fathers' ) [String: 'four score and seven years ago our four fathers'] > b = copy( a ) // fails because of of sins against the father { [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' } |
使用默认值(历史上特定于nodejs,但由于现代js,现在可以从浏览器中使用):
1 2 3 | import defaults from 'object.defaults'; const myCopy = defaults({}, myObject); |
从Apple javascript编码指南:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // Create an inner object with a variable x whose default // value is 3. function innerObj() { this.x = 3; } innerObj.prototype.clone = function() { var temp = new innerObj(); for (myvar in this) { // this object does not contain any objects, so // use the lightweight copy code. temp[myvar] = this[myvar]; } return temp; } // Create an outer object with a variable y whose default // value is 77. function outerObj() { // The outer object contains an inner object. Allocate it here. this.inner = new innerObj(); this.y = 77; } outerObj.prototype.clone = function() { var temp = new outerObj(); for (myvar in this) { if (this[myvar].clone) { // This variable contains an object with a // clone operator. Call it to create a copy. temp[myvar] = this[myvar].clone(); } else { // This variable contains a scalar value, // a string value, or an object with no // clone function. Assign it directly. temp[myvar] = this[myvar]; } } return temp; } // Allocate an outer object and assign non-default values to variables in // both the outer and inner objects. outer = new outerObj; outer.inner.x = 4; outer.y = 16; // Clone the outer object (which, in turn, clones the inner object). newouter = outer.clone(); // Verify that both values were copied. alert('inner x is '+newouter.inner.x); // prints 4 alert('y is '+newouter.y); // prints 16 |
史蒂夫
在我的代码中,我经常定义一个函数(u)来处理副本,这样我就可以将"按值"传递给函数。此代码创建一个深度副本,但保持继承。它还跟踪子副本,以便在不进行无限循环的情况下复制自引用对象。请随意使用。
它可能不是最优雅的,但它还没有让我失望。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | _ = function(oReferance) { var aReferances = new Array(); var getPrototypeOf = function(oObject) { if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject); var oTest = new Object(); if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__; if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype; return Object.prototype; }; var recursiveCopy = function(oSource) { if(typeof(oSource)!=="object") return oSource; if(oSource===null) return null; for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1]; var Copy = new Function(); Copy.prototype = getPrototypeOf(oSource); var oCopy = new Copy(); aReferances.push([oSource,oCopy]); for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]); return oCopy; }; return recursiveCopy(oReferance); }; // Examples: Wigit = function(){}; Wigit.prototype.bInThePrototype = true; A = new Wigit(); A.nCoolNumber = 7; B = _(A); B.nCoolNumber = 8; // A.nCoolNumber is still 7 B.bInThePrototype // true B instanceof Wigit // true |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | // // creates 'clone' method on context object // // var // clon = Object.clone( anyValue ); // !((function (propertyName, definition) { this[propertyName] = definition(); }).call( Object, "clone", function () { function isfn(fn) { return typeof fn ==="function"; } function isobj(o) { return o === Object(o); } function isarray(o) { return Object.prototype.toString.call(o) ==="[object Array]"; } function fnclon(fn) { return function () { fn.apply(this, arguments); }; } function owns(obj, p) { return obj.hasOwnProperty(p); } function isemptyobj(obj) { for (var p in obj) { return false; } return true; } function isObject(o) { return Object.prototype.toString.call(o) ==="[object Object]"; } return function (input) { if (isfn(input)) { return fnclon(input); } else if (isobj(input)) { var cloned = {}; for (var p in input) { owns(Object.prototype, p) || ( isfn(input[p]) && ( cloned[p] = function () { return input[p].apply(input, arguments); } ) || ( cloned[p] = input[p] ) ); } if (isarray(input)) { cloned.length = input.length; "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift" .split("") .forEach( function (methodName) { isfn( Array.prototype[methodName] ) && ( cloned[methodName] = function () { return Array.prototype[methodName].apply(cloned, arguments); } ); } ); } return isemptyobj(cloned) ? ( isObject(input) ? cloned : input ) : cloned; } else { return input; } }; } )); // |
我不知道这对哪些情况不起作用,但它给了我一个数组的副本。我觉得它很可爱:)希望它有帮助
1 | copiedArr = origArr.filter(function(x){return true}) |
由于同样的问题,我来到这个页面,但我既没有使用jquery,也没有任何克隆方法适用于我自己的对象。
我知道我的答案与这个问题没有太大的关联,因为这是一个不同的方法。我使用的不是克隆函数,而是创建函数。它对我有以下作用(不幸的是限制了):
首先,我这样定义我的对象:
1 2 3 4 | var obj= new Object(); obj.Type='Row'; obj.ID=1; obj.Value='Blah blah'; |
现在我移动了所有东西,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function getObjSelektor(id_nummer,selected){ var obj = document.createElement("select"); obj.setAttribute("id","Selektor_"+id_nummer); obj.setAttribute("name","Selektor"); obj.setAttribute("size","1"); var obj_opt_1 = document.createElement("option"); obj_opt_1.setAttribute("value","1"); if(1==selected) posopval_opt_1.setAttribute("selected","selected"); obj_opt_1.innerHTML="Blah blah"; obj.appendChild(obj_opt_1); var obj_opt_2 = document.createElement("option"); obj_opt_2.setAttribute("value","2"); if(2==selected) obj_opt_2.setAttribute("selected","selected"); obj_opt_2.innerHTML="2nd Row"; obj.appendChild(obj_opt_2); ... return obj; } |
并用常规代码调用函数:
1 | myDiv.getObjSelektor(getObjSelektor(anotherObject.ID)); |
如前所述,这是一种不同的方法,它为我的目的解决了我的问题。
如果您有一个带有函数的对象,您可以使用jsonfn来完成它,请参见http://www.eslinstrutor.net/jsonfn/。
1 2 3 4 5 6 7 | var obj= { name:'Marvin', getName : function(){ return this.name; } } var cobj = JSONfn.parse(JSONfn.stringify(obj)); |
The problem with copying an object that, eventually, may point at itself, can be solved with a simple check. Add this check, every time there is a copy action. It may be slow, but it should work.
I use a toType() function to return the object type, explicitly. I also have my own copyObj() function, which is rather similar in logic, which answers all three Object(), Array(), and Date() cases.
I run it in NodeJS.
NOT TESTED, YET.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // Returns true, if one of the parent's children is the target. // This is useful, for avoiding copyObj() through an infinite loop! function isChild(target, parent) { if (toType(parent) == '[object Object]') { for (var name in parent) { var curProperty = parent[name]; // Direct child. if (curProperty = target) return true; // Check if target is a child of this property, and so on, recursively. if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') { if (isChild(target, curProperty)) return true; } } } else if (toType(parent) == '[object Array]') { for (var i=0; i < parent.length; i++) { var curItem = parent[i]; // Direct child. if (curItem = target) return true; // Check if target is a child of this property, and so on, recursively. if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') { if (isChild(target, curItem)) return true; } } } return false; // Not the target. } |
我提供了这个问题的答案,因为我在这里没有看到任何本地的递归实现来解决
存在的问题是,
如果你的物品是安全简单的
1 2 3 | { '123':456 } |
…那么这里的任何其他答案都可能有效。
但如果你有…
1 2 3 4 | { '123':<reactJSComponent>, '456':document.createElement('div'), } |
…然后你需要这样的东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // cloneVariable() : Clone variable, return null for elements or components. var cloneVariable = function (args) { const variable = args.variable; if(variable === null) { return null; } if(typeof(variable) === 'object') { if(variable instanceof HTMLElement || variable.nodeType > 0) { return null; } if(Array.isArray(variable)) { var arrayclone = []; variable.forEach((element) => { arrayclone.push(cloneVariable({'variable':element})); }); return arrayclone; } var objectclone = {}; Object.keys(variable).forEach((field) => { objectclone[field] = cloneVariable({'variable':variable[field]}); }); return objectclone; } return variable; } |
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. It will return the target object.
1 2 3 4 5 6 7 8 9 10 | const target = { a: 1, b: 2 }; const source = { b: 4, c: 5 }; const returnedTarget = Object.assign(target, source); console.log(target); // expected output: Object { a: 1, b: 4, c: 5 } console.log(returnedTarget); // expected output: Object { a: 1, b: 4, c: 5 } |
Syntax
1 | Object.assign(target, ...sources) |
所以它将调用getter和setter。因此,它分配属性,而不只是复制或定义新属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型中,应使用
同时复制字符串和符号属性。
例如,在出现错误的情况下,如果属性不可写,则会引发类型错误,如果在引发错误之前添加了任何属性,则可以更改目标对象。
请注意,
可以克隆对象而不修改父对象-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** [Object Extend]*/ ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) { for ( var property in source ) destination[property] = source[property]; return destination; } ) ); /** [/Object Extend]*/ /** [Object clone]*/ ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) { return this.extend( {}, object ); } ) ); /** [/Object clone]*/ let myObj = { a:1, b:2, c:3, d:{ a:1, b:2, c:3 } }; let clone = Object.clone( myObj ); clone.a = 10; console.log('clone.a==>', clone.a); //==> 10 console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here let clone2 = Object.clone( clone ); clone2.a = 20; console.log('clone2.a==>', clone2.a); //==> 20 console.log('clone.a==>', clone.a); //==> 10 // object not modified here |
如果您使用的是typescript,需要支持较旧的Web浏览器(因此不能使用
1 2 3 4 5 6 | /** Creates a new object that combines the properties of the specified objects. */ function combine(...objs: {}[]) { const combined = {}; objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p])); return combined; } |
如果您的对象是一个类(例如https://developer.mozilla.org/en-us/docs/web/javascript/reference/classes):
1 2 | var copiedObject = jQuery.extend(true, {}, originalObject); copiedObject.__proto__ = originalObject.__proto__; |
然后在
要处理
1 | var clone = JSOG.parse(JSOG.stringify(original)); |
尝试用这个技巧修补JSOG进行克隆可能也很有趣(目前没有时间,但如果有人想尝试一下…):
序列化简单函数:
1 2 | foo.f = function(a) { return a } var stringForm = foo.f.toString() //"function (a) { return a }" |
反序列化函数:
1 | eval("foo.f =" + stringForm) |
需要一些约定(可能以属性的名义)来标识函数与常规字符串(可能是
当然,如果函数调用第二个函数,则第二个函数将需要与原始函数一样存在。
但是,如果您要接受来自不受信任的源的序列化表单,那么接受来自不受信任的源的任何表单中的任何函数是非常危险的,因此,如果您对克隆函数感兴趣,那么信任必须已经建立(或者您已经打算写一个安全缺陷!).
免责声明:我没有测试JSOG stringify/parse与JSON stringify/parse的速度,但它确实适用于我测试过的简单(循环)对象。
好吧,我知道它有很多答案,但是没有人指出,ecmascript5有赋值方法,在ff和chrome上工作,它复制可枚举的和自己的属性和符号。
对象赋值
我最喜欢的优雅的JS对象克隆解决方案是
1 2 3 4 5 | function CloneObject() {} function cloneObject(o) { CloneObject.prototype = o; return new CloneObject(); } |
使用
与许多复制解决方案不同,此克隆在克隆对象中保留原型关系。
1 2 3 4 5 6 | function clone(obj) { var cloneObj = Object.create(obj); return cloneObj; } |
在javascript对象中,单独继承另一个对象(原型继承)。create(obj)返回一个对象,该对象是obj的子对象或子对象。在上面的函数中,它将有效地返回对象的副本。
然而,这是一种非常奇怪的克隆方法,因为我没有将继承用于它的真正目的。