我有一个对象, x
。我想把它复制为对象 y
,这样改变 y
不要修改 x
。我意识到复制从内置JavaScript对象派生的对象将导致额外的,不需要的属性。这不是问题,因为我正在复制我自己的一个文字构造的对象。
如何正确克隆JavaScript对象?
我有一个对象, x
。我想把它复制为对象 y
,这样改变 y
不要修改 x
。我意识到复制从内置JavaScript对象派生的对象将导致额外的,不需要的属性。这不是问题,因为我正在复制我自己的一个文字构造的对象。
如何正确克隆JavaScript对象?
只是用 Object.assign() 如建议 这里
但请注意,这只是一个浅拷贝。嵌套对象仍被复制为引用。
为JavaScript中的任何对象执行此操作并不简单或直接。您将遇到错误地从对象原型中拾取属性的问题,该属性应保留在原型中而不会复制到新实例。例如,如果您要添加 clone
方法 Object.prototype
,如某些答案所示,您需要明确跳过该属性。但是如果添加其他额外的方法会怎么样呢 Object.prototype
,或其他你不了解的中间原型?在这种情况下,您将复制您不应该使用的属性,因此您需要使用。来检测无法预料的非本地属性 hasOwnProperty
方法。
除了不可枚举的属性,当您尝试复制具有隐藏属性的对象时,您将遇到更严峻的问题。例如, prototype
是函数的隐藏属性。此外,使用属性引用对象的原型 __proto__
,也是隐藏的,并且不会被遍历源对象属性的for / in循环复制。我认为 __proto__
可能是Firefox的JavaScript解释器特有的,它可能在其他浏览器中有所不同,但你得到了图片。并非一切都是可以计算的。如果您知道其名称,则可以复制隐藏属性,但我不知道有任何方法可以自动发现它。
寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是 Object
,然后简单地创建一个新的通用对象 {}
会工作,但如果源的原型是一些后代 Object
,那么你将会错过你使用该跳过的原型中的其他成员 hasOwnProperty
过滤器,或者原型中的过滤器,但首先不是可枚举的。一种解决方案可能是调用源对象 constructor
获取初始复制对象然后复制属性的属性,但是您仍然不会获得不可枚举的属性。例如,a Date
object将其数据存储为隐藏成员:
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
将落后5秒 d2
。一种制作方法 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对象,但它可能足以用于许多目的,只要您不认为它只适用于您抛出的任何内容。
使用jQuery,你可以 浅拷贝 同 延伸:
var copiedObject = jQuery.extend({}, originalObject)
对copiedObject的后续更改不会影响originalObject,反之亦然。
或者做一个 深刻的副本:
var copiedObject = jQuery.extend(true, {}, originalObject)
如果您不在对象中使用函数,则可以使用以下非常简单的内容:
var cloneOfA = JSON.parse(JSON.stringify(a));
这适用于包含对象,数组,字符串,布尔值和数字的所有类型的对象。
也可以看看 本文关于浏览器的结构化克隆算法 在向工作人员发送消息和从工作人员发布消息时使用。它还包含深度克隆功能。
在ECMAScript 6中有 Object.assign 方法,它将所有可枚举的自有属性的值从一个对象复制到另一个对象。例如:
var x = {myProp: "value"};
var y = Object.assign({}, x);
但请注意,嵌套对象仍会被复制为引用。
答案很多,但没有提到 的Object.create 来自ECMAScript 5,它肯定不会给你一个精确的副本,但将源设置为新对象的原型。
因此,这不是问题的确切答案,但它是一个单行解决方案,因而优雅。它适用于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。
一个 Object.assign
method是ECMAScript 2015(ES6)标准的一部分,完全符合您的要求。
var clone = Object.assign({}, obj);
Object.assign()方法用于将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象。
该 填充工具 支持旧浏览器:
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;
}
});
}
每 MDN:
Object.assign({}, a)
JSON.parse(JSON.stringify(a))
不需要外部库,但您需要检查 首先是浏览器兼容。