我有一个对象x
。 我想将其复制为对象y
,以使对y
更改不会修改x
。 我意识到,复制从内置JavaScript对象派生的对象将导致额外的不需要的属性。 这不是问题,因为我正在复制自己的文字构造对象之一。
如何正确克隆JavaScript对象?
#1楼
您可以克隆对象,并使用单行代码从上一个引用中删除任何引用。 只需做:
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();
};
}
#2楼
我只是想在这篇文章中添加到所有Object.create
解决方案中,以至于Nodejs无法以所需的方式工作。
在Firefox中,
var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´
是
{test:"test"}
。
在nodejs中
{}
#3楼
如果您不在对象中使用Date
,function,undefined或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()
这适用于包含对象,数组,字符串,布尔值和数字的所有类型的对象。
另请参阅本文有关浏览器的结构化克隆算法的文章,该算法在与工作人员之间发布消息时使用。 它还包含用于深度克隆的功能。
#4楼
如果您可以使用浅表副本,那么underscore.js库提供了一个clone方法。
y = _.clone(x);
或者你可以像扩展它
copiedObject = _.extend({},originalObject);
#5楼
这是对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));
#6楼
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;
};
#7楼
由于mindeavor指出要克隆的对象是“文字构造的”对象,因此一种解决方案可能是简单地多次生成该对象,而不是克隆该对象的实例:
function createMyObject()
{
var myObject =
{
...
};
return myObject;
}
var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
#8楼
使用Lodash:
var y = _.clone(x, true);
#9楼
一种特别不优雅的解决方案是使用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
#10楼
互联网上的大多数解决方案都有几个问题。 因此,我决定进行跟进,包括为什么不接受接受的答案。
起始情况
我想复制一个Javascript Object
及其所有子Object
及其子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
就像IE 8一样,在旧版浏览器中还有一个针对Object.create
的polyfill。这有点像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
与本机解决方案相同的问题,但输出会差一些。
更好(但不是完美)的解决方案
进行深入研究时,我发现了与此问题类似的问题( 在Javascript中,当执行深度复制时,由于属性为“ 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
,但在克隆时会弄乱它们的实例。
#11楼
对于使用AngularJS的用户,此库中还有直接方法来克隆或扩展对象。
var destination = angular.copy(source);
要么
angular.copy(source, destination);
更多angular.copy 文档 ...
#12楼
我已经编写了自己的实现。 不确定是否算是更好的解决方案:
/*
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) /
*/
以下是实现:
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;
}
#13楼
在ECMAScript 6中,存在Object.assign方法,该方法将所有可枚举的自身属性的值从一个对象复制到另一个对象。 例如:
var x = {myProp: "value"};
var y = Object.assign({}, x);
但是请注意,嵌套对象仍会复制为引用。
#14楼
对克隆简单对象感兴趣:
JSON.parse(JSON.stringify(json_original));
源: 如何不通过引用将JavaScript对象复制到新变量?
#15楼
在一行代码中克隆Javascript对象的一种优雅方法
Object.assign
方法是ECMAScript 2015(ES6)标准的一部分,可以完全满足您的需求。
var clone = Object.assign({}, obj);
Object.assign()方法用于将所有可枚举的自身属性的值从一个或多个源对象复制到目标对象。
支持旧版浏览器的polyfill :
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;
}
});
}
#16楼
您可以简单地使用spread属性复制没有引用的对象。 但是要小心(请参阅注释),“副本”仅位于最低的对象/数组级别。 嵌套属性仍然是参考!
完整克隆:
let x = {a: 'value1'}
let x2 = {...x}
// => mutate without references:
x2.a = 'value2'
console.log(x.a) // => 'value1'
克隆二级引用:
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:
#17楼
每个MDN :
- 如果要浅拷贝,请使用
Object.assign({}, a)
- 对于“深层”副本,请使用
JSON.parse(JSON.stringify(a))
不需要外部库,但您需要首先检查浏览器的兼容性 。
#18楼
旧问题的新答案! 如果您高兴地将ECMAScript 2016(ES6)与Spread Syntax一起使用 ,这很容易。
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
这为对象的浅表副本提供了一种干净的方法。 进行深层复制(意味着要递归地嵌套每个对象中的每个值的新副本)需要上述较重的解决方案。
JavaScript不断发展。
#19楼
在ES-6中,您可以简单地使用Object.assign(...)。 例如:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
一个很好的参考在这里: https : //googlechrome.github.io/samples/object-assign-es6/
#20楼
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
ES6解决方案,如果您想(浅)克隆一个类实例而不仅仅是一个属性对象。
#21楼
好的,假设您在下面有这个对象并且想要克隆它:
let obj = {a:1, b:2, c:3}; //ES6
要么
var obj = {a:1, b:2, c:3}; //ES5
答案主要取决于您使用哪种ECMAscript ,在ES6+
,您可以简单地使用Object.assign
进行克隆:
let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};
或使用像这样的传播算子:
let cloned = {...obj}; //new {a:1, b:2, c:3};
但是,如果使用ES5
,则可以使用几种方法,但是可以使用JSON.stringify
,只需确保不使用要复制的大量数据即可,但是在许多情况下,它可能是一种方便的方式,如下所示:
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
#22楼
使用jQuery,您可以使用extend进行浅表复制 :
var copiedObject = jQuery.extend({}, originalObject)
随后对copiedObject
更改将不会影响originalObject
,反之亦然。
或进行深复制 :
var copiedObject = jQuery.extend(true, {}, originalObject)
#23楼
在ECMAScript 2018中
let objClone = { ...obj };
请注意, 嵌套对象仍将被复制为参考。
#24楼
我认为有一个简单可行的答案。 在深度复制中,有两个问题:
- 使属性彼此独立。
- 并使方法在克隆对象上保持活动状态。
因此,我认为一个简单的解决方案是先进行序列化和反序列化,然后再对其进行赋值以复制函数。
let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);
尽管这个问题有很多答案,但我希望这个问题也能有所帮助。
#25楼
对于深层复制和克隆,先JSON.stringify然后再JSON.parse对象:
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
#26楼
摘自本文: 如何在Java语言中复制数组和对象,作者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;
};
#27楼
这是您可以使用的功能。
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;
}
#28楼
在JavaScript中对任何对象执行此操作都不是简单或直接的。 您将遇到错误地从对象的原型中选取应保留在原型中而不应复制到新实例的属性的问题。 例如,如果要将clone
方法添加到Object.prototype
,如一些答案所示,则需要显式跳过该属性。 但是,如果还有其他您不知道的其他方法添加到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
与另一个Date
相同的方法是调用setTime
方法,但这特定于Date
类。 尽管我很乐意弄错,但我认为没有解决该问题的通用解决方案!
当我不得不实施常规的深层复制时,以假定只复制一个简单的Object
, Array
, Date
, String
, Number
或Boolean
来折衷。 后三种类型是不可变的,因此我可以执行浅表复制,而不必担心它会更改。 我进一步假设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对象,但只要您不认为它会对您扔给它的任何东西起作用,它就可以满足许多目的。
#29楼
答: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的注释。
#30楼
有很多答案,但是没有一个提到ECMAScript 5中的Object.create ,它当然没有提供确切的副本,但是将源设置为新对象的原型。
因此,这不是对该问题的确切答案,而是单线解决方案,因此很优雅。 它最适合2种情况:
- 此类继承有用的地方(du!)
- 不会修改源对象的地方,因此这两个对象之间的关系就不成问题了。
例:
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。
#31楼
上面的答案很接近JanTuroň,由于兼容性问题,它可能是在浏览器中最好的选择,但它可能会引起一些奇怪的枚举问题。 例如,执行:
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。