如何正确克隆 JavaScript 对象?
Posted
技术标签:
【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:我有一个对象x
。我想将它复制为对象y
,这样对y
的更改就不会修改x
。我意识到复制从内置 javascript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。
【问题讨论】:
看到这个问题:***.com/questions/122102/… 对于 JSON,我使用mObj=JSON.parse(JSON.stringify(jsonObject));
我真的不明白为什么没有人建议Object.create(o)
,它可以满足作者的所有要求?
var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2;
执行此操作后,y.deep.key
也将为 2,因此 Object.create 不能用于克隆...
@r3wt 这不起作用...请仅在对解决方案进行基本测试后发布..
【参考方案1】:
来自这篇文章:How to copy arrays and objects in Javascript by Brian Huisman:
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;
;
【讨论】:
这很接近,但不适用于任何对象。尝试用这个克隆一个 Date 对象。并非所有属性都是可枚举的,因此它们不会全部显示在 for/in 循环中。 像这样添加到对象原型对我来说破坏了 jQuery。即使我重命名为 clone2。 @iPadDeveloper2011 上面的代码有一个错误,它创建了一个名为“i”“(for i in this)”的全局变量,而不是“(for var i in this)”。我有足够的业力来编辑它并修复它,所以我做到了。 @Calvin: 这应该被创建一个不可枚举的属性,否则 'clone' 将出现在 'for' 循环中。 为什么var copiedObj = Object.create(obj);
不是一个好方法?【参考方案2】:
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;
【讨论】:
这个答案非常接近,但并不完全正确。如果您尝试克隆 Date 对象,您将不会获得相同的日期,因为对 Date 构造函数的调用会使用当前日期/时间初始化新 Date。该值不可枚举,也不会被 for/in 循环复制。 并不完美,但对于那些基本情况来说很好。例如。允许对可以是基本对象、数组或字符串的参数进行简单克隆。 赞成使用new
正确调用构造函数。接受的答案没有。
适用于其他所有节点!仍然留下参考链接
递归的想法很棒。但是如果值是数组,它会工作吗?【参考方案3】:
如果您的对象中没有循环依赖,我建议使用其他答案之一或jQuery's copy methods,因为它们看起来都非常有效。
如果存在循环依赖关系(即,两个子对象相互链接),那么(从理论的角度)no way to solve this issue elegantly,您就有点搞砸了。
【讨论】:
实际上,Python 的对象序列化通过跟踪对象图中已经处理过的节点来处理循环引用。您可以使用该方法来实现强大的复制例程。不过,这将是更多的工作!【参考方案4】:来自Apple JavaScript Coding Guidelines:
// 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
史蒂夫
【讨论】:
没有克隆方法的对象属性将被此代码浅拷贝。因此,对原件的更改会影响副本。所以这并不能解决问题。【参考方案5】:对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向Object.prototype
添加clone
方法,正如某些答案所描述的那样,您将需要显式跳过该属性。但是,如果在Object.prototype
或其他中间原型中添加了您不知道的其他附加方法怎么办?在这种情况下,您将复制不应该复制的属性,因此您需要使用 hasOwnProperty
方法检测不可预见的非本地属性。
除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。例如,prototype
是函数的隐藏属性。此外,对象的原型由属性__proto__
引用,该属性也是隐藏的,并且不会被迭代源对象属性的for/in 循环复制。我认为__proto__
可能特定于 Firefox 的 JavaScript 解释器,在其他浏览器中可能有所不同,但你明白了。并非所有事物都是可枚举的。如果您知道它的名称,您可以复制隐藏的属性,但我不知道有什么方法可以自动发现它。
寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是Object
,那么只需使用 创建一个新的通用对象即可,但如果源的原型是
Object
的某个后代,那么您将丢失该原型中的其他成员您使用hasOwnProperty
过滤器跳过了哪些,或者哪些在原型中,但一开始就无法枚举。一种解决方案可能是调用源对象的constructor
属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,Date
对象将其数据存储为隐藏成员:
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() + "\nd2 = " + d2.toString());
, 5000);
d1
的日期字符串将比 d2
的日期字符串晚 5 秒。一种使Date
与另一个相同的方法是调用setTime
方法,但这是特定于Date
类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!
当我不得不实现一般深度复制时,我最终妥协,假设我只需要复制一个普通的Object
、Array
、Date
、String
、Number
或 Boolean
.最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设Object
或Array
中包含的任何元素也将是该列表中的6 个简单类型之一。这可以通过如下代码来完成:
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 种简单类型。也就是说,对象中对相同数据的引用不超过一个。例如:
// 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 对象,但它可能足以满足多种用途,只要您不认为它只适用于您扔给它的任何东西。
【讨论】:
缺少符号键和符号值。现在,使用Object.getOwnPropertyDescriptors
更好。【参考方案6】:
一个特别不优雅的解决方案是使用 JSON 编码来制作没有成员方法的对象的深层副本。该方法是对您的目标对象进行 JSON 编码,然后通过对其进行解码,您可以获得所需的副本。您可以根据需要进行多次解码,制作尽可能多的副本。
当然,函数不属于 JSON,所以这只适用于没有成员方法的对象。
这种方法非常适合我的用例,因为我将 JSON blob 存储在键值存储中,并且当它们在 JavaScript API 中作为对象公开时,每个对象实际上都包含原始状态的副本对象,这样我们就可以在调用者改变暴露的对象后计算增量。
var object1 = key:"value";
var object2 = object1;
object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);
object2.key = "a change";
console.log(object1);// returns value
【讨论】:
为什么函数不属于JSON?我不止一次看到它们以 JSON 格式传输... 函数不是 JSON 规范的一部分,因为它们不是一种安全(或智能)的数据传输方式,而这正是 JSON 的用途。我知道 Firefox 中的原生 JSON 编码器只是忽略传递给它的函数,但我不确定其他人的行为。 @mark: 'foo': function() return 1;
是文字构造的对象。
@abarnert 函数不是数据。 “函数字面量”用词不当——因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。【参考方案7】:
使用jQuery,您可以浅拷贝 extend:
var copiedObject = jQuery.extend(, originalObject)
对copiedObject
的后续更改不会影响originalObject
,反之亦然。
或者制作一个深拷贝:
var copiedObject = jQuery.extend(true, , originalObject)
【讨论】:
甚至:var copiedObject = jQuery.extend(,originalObject);
将 true 指定为深层复制的第一个参数也很有用:jQuery.extend(true, , originalObject);
【参考方案8】:
A.Levy 的回答差不多完成了,这是我的一点贡献:有一种方法可以处理递归引用,请看这一行
if(this[attr]==this) copy[attr] = copy;
如果对象是 XML DOM 元素,我们必须使用 cloneNode 代替
if(this.cloneNode) return this.cloneNode(true);
受 A.Levy 详尽的研究和 Calvin 的原型制作方法的启发,我提供了以下解决方案:
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;
另请参阅答案中的 Andy Burke 注释。
【讨论】:
Date.prototype.clone = function() return new Date(+this);
【参考方案9】:
有很多答案,但没有一个提到 ECMAScript 5 中的Object.create,它诚然没有给你一个精确的副本,但将源设置为新对象的原型。
因此,这不是问题的确切答案,但它是一种单行解决方案,因此很优雅。它最适用于 2 种情况:
-
这种继承在哪里有用(呵呵!)
源对象不会被修改,因此这两个对象之间的关系不成问题。
例子:
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。
【讨论】:
这是原型继承,不是克隆。这些是完全不同的东西。新对象没有任何自己的属性,它只是指向原型的属性。克隆的重点是创建一个不引用另一个对象中任何属性的全新对象。【参考方案10】:Jan Turoň 上面的答案非常接近,由于兼容性问题,可能最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题。例如,执行:
for ( var i in someArray ) ...
在遍历数组元素后将 clone() 方法分配给 i。这是避免枚举并与node.js一起使用的改编:
Object.defineProperty( Object.prototype, "clone",
value: 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;
);
Object.defineProperty( Date.prototype, "clone",
value: function()
var copy = new Date();
copy.setTime( this.getTime() );
return copy;
);
Object.defineProperty( Number.prototype, "clone", value: function() return this; );
Object.defineProperty( Boolean.prototype, "clone", value: function() return this; );
Object.defineProperty( String.prototype, "clone", value: function() return this; );
这避免了使 clone() 方法可枚举,因为 defineProperty() 默认可枚举为 false。
【讨论】:
【参考方案11】:您可以使用一行代码克隆一个对象并从前一个对象中删除任何引用。只需这样做:
var obj1 = text: 'moo1' ;
var obj2 = Object.create(obj1); // Creates a new clone without references
obj2.text = 'moo2'; // Only updates obj2's text property
console.log(obj1, obj2); // Outputs: obj1: text:'moo1', obj2: text:'moo2'
对于当前不支持 Object.create 的浏览器/引擎,您可以使用这个 polyfill:
// Polyfill Object.create if it does not exist
if (!Object.create)
Object.create = function (o)
var F = function () ;
F.prototype = o;
return new F();
;
【讨论】:
+1Object.create(...)
似乎绝对是要走的路。
完美答案。也许您可以为Object.hasOwnProperty
添加解释?这样人们就知道如何防止搜索原型链接。
效果很好,但是 polyfill 在哪些浏览器中工作?
这是用 obj1 作为原型创建 obj2。它之所以有效,是因为您正在遮蔽 obj2 中的 text
成员。您不是在制作副本,只是在 obj2 上找不到成员时遵循原型链。
这不会在“没有引用的情况下”创建它,它只是将引用移动到原型。它仍然是一个参考。如果原始属性发生变化,“克隆”中的原型属性也会发生变化。它根本不是克隆。【参考方案12】:
我只是想在这篇文章中添加到所有Object.create
解决方案,这不适用于nodejs。
在 Firefox 中的结果
var a = "test":"test";
var b = Object.create(a);
console.log(b);´
是
test:"test"
.
在nodejs中是
【讨论】:
这是原型继承,不是克隆。 @d13 虽然您的论点有效,但请注意,JavaScript 中没有克隆对象的标准化方法。这是原型继承,但如果您了解这些概念,它仍然可以用作克隆。 @froginvasion。使用 Object.create 的唯一问题是嵌套对象和数组只是对原型嵌套对象和数组的指针引用。 jsbin.com/EKivInO/2/edit?js,console。从技术上讲,“克隆”对象应该有自己独特的属性,这些属性不是对其他对象属性的共享引用。 @d13 好的,我现在明白你的意思了。但我的意思是,太多人对原型继承的概念感到疏远,对我来说无法了解它是如何工作的。如果我没记错的话,可以通过调用Object.hasOwnProperty
检查您是否拥有该数组来修复您的示例。是的,这确实增加了处理原型继承的额外复杂性。【参考方案13】:
如果你不在你的对象中使用Date
s、functions、undefined、regExp 或 Infinity,一个非常简单的衬线是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'
console.log(a);
console.log(typeof a.date); // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date); // result of .toISOString()
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。
另请参阅this article about the structured clone algorithm of browsers,它在向工作人员发送消息或从工作人员发送消息时使用。它还包含一个深度克隆功能。
【讨论】:
有时最好的答案是最简单的。天才。【参考方案14】:如果你对浅拷贝没问题,underscore.js 库有一个clone 方法。
y = _.clone(x);
或者你可以像这样扩展它
copiedObject = _.extend(,originalObject);
【讨论】:
谢谢。在 Meteor 服务器上使用这种技术。 要快速开始使用 lodash,我建议学习 npm、Browserify 以及 lodash。我让克隆与 'npm i --save lodash.clone' 一起工作,然后是 'var clone = require('lodash.clone');'要让 require 工作,你需要类似 browserify 的东西。安装并了解它的工作原理后,每次运行代码时都将使用“browserify yourfile.js > bundle.js;start chrome index.html”(而不是直接进入 Chrome)。这会将您的文件和 npm 模块所需的所有文件收集到 bundle.js 中。不过,您可能可以使用 Gulp 节省时间并自动执行此步骤。【参考方案15】:这是对 A. Levy 的代码的改编,也可以处理函数的克隆和多个/循环引用 - 这意味着如果克隆的树中的两个属性是同一对象的引用,则克隆的对象树将使这些属性指向被引用对象的同一个克隆。这也解决了循环依赖的情况,如果不加以处理,就会导致无限循环。算法复杂度为O(n)
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;
一些快速测试
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));
【讨论】:
截至 2016 年 9 月,这是该问题的唯一正确解决方案。【参考方案16】: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;
;
【讨论】:
if(!src && typeof src != "object")
。我认为应该是||
而不是&&
。【参考方案17】:
由于mindeavor 声明要克隆的对象是“文字构造”对象,因此解决方案可能是简单地生成该对象多次,而不是克隆该对象的一个实例:
function createMyObject()
var myObject =
...
;
return myObject;
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
【讨论】:
【参考方案18】:使用 Lodash:
var y = _.clone(x, true);
【讨论】:
天哪,重新发明克隆技术简直太疯狂了。这是唯一理智的答案。 我更喜欢_.cloneDeep(x)
,因为它本质上和上面一样,但读起来更好。【参考方案19】:
在我的代码中,我经常定义一个function (_)
来处理副本,以便我可以将by value
传递给函数。此代码创建一个深层副本,但保持继承。它还跟踪子副本,以便可以在没有无限循环的情况下复制自引用对象。随意使用。
它可能不是最优雅的,但它还没有让我失望。
_ = 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
【讨论】:
【参考方案20】:请咨询http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data,了解 W3C 的“结构化数据的安全传递”算法,该算法旨在由浏览器实现,用于将数据传递给网络工作者等。但是,它有一些限制,因为它不处理函数。请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm 了解更多信息,包括 JS 中的另一种算法,它可以让您部分实现。
【讨论】:
虽然这有一些很棒的链接,但这并不是一个真正的答案。如果它被扩展为包含引用的算法的实现,它可能是一个答案。【参考方案21】:我最喜欢和优雅的 JS 对象克隆解决方案是
function CloneObject()
function cloneObject(o)
CloneObject.prototype = o;
return new CloneObject();
使用cloneObject(object)
获取 JS 对象的克隆。
与许多复制解决方案不同,此克隆将原型关系保留在克隆对象中。
【讨论】:
我认为这不能回答操作员发布的问题。code
var o1 = a: 1 var o2 = cloneObject(o1) o2.b = 2 console.log(o1) // 将是 a:1, b:2 code
这和Object.create
一样。
这是原型继承,不是克隆。
@d13——对很多人来说,这就是克隆。复制属性就是复制。
你能确定实现会总是在实例化时将原型引用复制到实例中吗?我不是说不会,但我有疑问。不管怎样,这只是以原始为原型制作一个空对象,这是不一样的。如果你不打算修改任何东西,那么你不需要克隆,如果你想保留一个快照,它也不会工作,因为原型的属性将会改变,因为原型 是原来的对象。【参考方案22】:
//
// 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;
;
));
//
【讨论】:
为什么这个答案比其他任何一个都好? 你是从任何框架或库中得到这个的,还是你创建的?我不知道,它对我来说似乎不可读。【参考方案23】:结构化克隆
见my answer to a near-duplicate question here。您可以使用 HTML 标准包含的相同结构化克隆机制在领域之间发送数据。很快您就可以通过新的structuredClone
全局方法来实现这一点。
const clone = structuredClone(original);
更多详情请见the other answer。
【讨论】:
+1 用于给出最终可能以何种形式内置的想法——即使现在无法使用。【参考方案24】:function clone(obj)
var cloneObj = Object.create(obj);
return cloneObj;
在 Javascript 中对象单独继承另一个对象(原型继承)。 Object.create(obj) 返回一个对象,它是 obj 的子对象或子对象。在上述函数中,它将有效地返回对象的副本。
但是,这是一种非常奇怪的克隆方式,因为我没有将继承用于其真正目的。
【讨论】:
这是原型继承,不是克隆。新对象没有任何自己的属性,它只是使用原型上的属性。 @d13 我不需要新对象的新属性,我只想要新对象中父对象的属性,那为什么不继承呢? 这可能非常有用,但它不是克隆,这就是本主题的主题。克隆是您将一个对象的属性显式复制到另一个对象中,以便新对象不再引用原型上的属性。这样做的原因是为了防止混淆属性是属于对象还是属于它的原型。如果您不确定这种行为,那么这种混淆可能会导致难以诊断的错误。 Check this JSBin example and explanation. @Shuaib - 因为如果您更改原始对象上的属性值,它们将在新对象上更改......它们没有分开......新对象没有“拥有”这些属性,它只是引用旧对象。 ...这意味着您不妨去var cloned = otherObj;
并以这种方式进行参考。在这两种情况下,新变量引用旧对象的唯一原因是因为它实际上是指向它的指针。一个只是点本身,而另一个持有原型上的指针。
@JimboJonny 递归 Object.create() 怎么样?【参考方案25】:
由于同样的问题,我来到了这个页面,但我既没有使用 JQuery,也没有一个克隆方法适用于我自己的对象。
我知道我的答案与这个问题的相关性不是太强,因为它是一种不同的方法。我不使用克隆函数,而是使用创建函数。它对我有以下(不幸的限制)目的:
-
我主要使用 JSP 生成的 Javascript
我一开始就知道必须生成哪个对象(在我的例子中,它是来自数据库的信息,它被获取一次,需要在 JS 中更频繁地部署。
首先我这样定义我的对象:
var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';
现在我移动了所有内容,例如:
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;
并在常规代码中调用函数:
myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));
如前所述,这是一种不同的方法,它为我的目的解决了我的问题。
【讨论】:
【参考方案26】:如果你有一个带有函数的对象,你可以用 JSONfn 来做,见http://www.eslinstructor.net/jsonfn/。
var obj=
name:'Marvin',
getName : function()
return this.name;
var cobj = JSONfn.parse(JSONfn.stringify(obj));
【讨论】:
【参考方案27】:您可以使用函数闭包来获得深拷贝的所有好处,而无需深拷贝。这是一个非常不同的范例,但效果很好。与其尝试复制现有对象,不如在需要时使用一个函数来实例化一个新对象。
首先,创建一个返回对象的函数
function template()
return
values: [1, 2, 3],
nest: x: a: "a", b: "b", y: 100
;
然后创建一个简单的浅拷贝函数
function copy(a, b)
Object.keys(b).forEach(function(key)
a[key] = b[key];
);
创建一个新对象,并将模板的属性复制到它上面
var newObject = ;
copy(newObject, template());
但是上面的复制步骤不是必须的。您需要做的就是:
var newObject = template();
现在您有了一个新对象,测试一下它的属性是什么:
console.log(Object.keys(newObject));
这显示:
["values", "nest"]
是的,这些是 newObject 自己的属性,而不是对另一个对象的属性的引用。 让我们检查一下:
console.log(newObject.nest.x.b);
这显示:
"b"
newObject 已获取模板对象的所有属性,但没有任何依赖链。
http://jsbin.com/ISUTIpoC/1/edit?js,console
我添加了这个例子来鼓励一些辩论,所以请添加一些 cmets :)
【讨论】:
Object.keys 直到 JavaScript 1.8.5 才实现,这意味着它在 IE 8 和其他旧版浏览器中不可用。因此,虽然这个答案在现代浏览器中效果很好,但在 IE 8 中会失败。所以如果你使用这种方法,你必须使用正确模拟 Object.keys polyfill。【参考方案28】:复制一个最终可能指向自身的对象的问题可以通过简单的检查来解决。添加此检查,每次有复制操作。它可能慢,但它应该工作。
我使用 toType() 函数显式地返回对象类型。我也有自己的 copyObj() 函数,它在逻辑上非常相似,它回答了所有三种 Object()、Array() 和 Date() 情况。
我在 NodeJS 中运行它。
尚未测试。
// 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.
【讨论】:
【参考方案29】:互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,其中包括为什么不应该接受已接受的答案。
开始情况
我想深度复制 Javascript Object
及其所有子级及其子级等等。但由于我不是一个普通的开发者,我的Object
有正常 properties
、circular structures
甚至nested objects
。
所以让我们先创建一个circular structure
和一个nested object
。
function Circ()
this.me = this;
function Nested(y)
this.y = y;
让我们将所有内容放在一个名为 a
的 Object
中。
var a =
x: 'a',
circ: new Circ(),
nested: new Nested('a')
;
接下来,我们要将a
复制到名为b
的变量中并对其进行变异。
var b = a;
b.x = 'b';
b.nested.y = 'b';
您知道这里发生了什么,否则您甚至不会想到这个好问题。
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"
现在让我们找到解决方案。
JSON
我尝试的第一次尝试是使用JSON
。
var b = JSON.parse( JSON.stringify( a ) );
b.x = 'b';
b.nested.y = 'b';
不要在上面浪费太多时间,你会得到TypeError: Converting circular structure to JSON
。
递归复制(接受的“答案”)
让我们看看接受的答案。
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.");
看起来不错,嗯?它是对象的递归副本,也可以处理其他类型,例如 Date
,但这不是必需的。
var b = cloneSO(a);
b.x = 'b';
b.nested.y = 'b';
递归和circular structures
不能很好地协同工作...RangeError: Maximum call stack size exceeded
原生解决方案
在与我的同事争论之后,我的老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫做Object.create
。
var b = Object.create(a);
b.x = 'b';
b.nested.y = 'b';
这个解决方案是前段时间添加到 Javascript 中的,甚至可以处理 circular structure
。
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
在旧版浏览器中,Object.create
有一个 polyfill,就像 IE 8 一样。这就像 Mozilla 推荐的那样,当然,它并不完美,并且会导致与 本机解决方案相同的问题.
function F() ;
function clonePF(o)
F.prototype = o;
return new F();
var b = clonePF(a);
b.x = 'b';
b.nested.y = 'b';
我已将F
放在范围之外,以便我们查看instanceof
告诉我们的内容。
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
与原生解决方案相同的问题,但输出稍差。
更好(但不完美)的解决方案
在四处挖掘时,我发现了一个与此问题类似的问题 (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?),但有一个更好的解决方案。
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';
让我们看看输出...
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
要求匹配,但还是有一些小问题,包括将nested
和circ
的instance
改为Object
。
共享一个叶子的树的结构不会被复制,它们会成为两个独立的叶子:
[Object] [Object]
/ \ / \
/ \ / \
|/_ _\| |/_ _\|
[Object] [Object] ===> [Object] [Object]
\ / | |
\ / | |
_\| |/_ \|/ \|/
[Object] [Object] [Object]
结论
使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真实深度副本。它处理简单的properties
、circular structures
和nested object
,但在克隆时会弄乱它们的实例。
jsfiddle
【讨论】:
所以结论是避免这个问题:) @mikus 直到有一个 真正的 规范,它涵盖的不仅仅是基本用例,是的。 对上面提供的解决方案进行了分析,但作者得出的结论表明该问题没有解决方案。 遗憾的是 JS 不包含原生克隆功能。 在所有最热门的答案中,我觉得这是接近正确的答案。【参考方案30】:对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。
var destination = angular.copy(source);
或
angular.copy(source, destination);
更多内容见 angular.copy documentation...
【讨论】:
这是一个深拷贝仅供参考。以上是关于如何正确克隆 JavaScript 对象?的主要内容,如果未能解决你的问题,请参考以下文章