我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
当前回答
老问题的新答案!如果您有幸将ECMAScript 2016(ES6)与Spread Syntax一起使用,这很简单。
keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}
这为对象的浅层副本提供了一种干净的方法。创建一个深度副本,意味着为每个递归嵌套对象中的每个值创建一个新副本,这需要使用上面更重的解决方案。
JavaScript不断发展。
其他回答
如果对象中没有循环依赖关系,我建议使用其他答案之一或jQuery的复制方法,因为它们看起来都很有效。
如果存在循环依赖关系(即,两个子对象彼此链接),那么你就有点糟糕了,因为(从理论角度)没有办法优雅地解决这个问题。
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)
ES6解决方案,如果您想要(浅层)克隆类实例而不仅仅是属性对象。
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;
};
基于模板克隆对象。如果您不想要精确的副本,但确实想要某种可靠的克隆操作的健壮性,但您只想要克隆位,或者希望确保可以控制每个属性值的存在或格式,该怎么办?
我之所以这么做,是因为它对我们有用,我们之所以创建它,是因为我们找不到类似的东西。您可以使用它来基于模板对象克隆对象,该模板对象指定了要克隆的对象的属性,如果源对象上不存在这些属性,或者您希望如何处理克隆,则模板允许函数将这些属性转换为不同的属性。如果没有用,我相信有人可以删除这个答案。
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;
}
一个简单的调用方法如下:
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属性,但它确保它是一个数字,即使它不存在于源对象上。
var template = {
ID: function (srcObj) {
if(srcObj.ID == undefined){ return -1; }
return parseInt(srcObj.ID.toString());
}
}
数组可以很好地进行克隆,但如果您愿意,也可以使用自己的函数来处理这些单独的属性,并执行以下特殊操作:
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;
}
}
所以在上面,我们的模板只复制源对象的标签属性(如果它存在的话)(假设它是一个数组),并且对该数组中的每个元素调用clone函数,根据第二个模板单独克隆它,该模板只复制这些标签元素的a和b属性。
如果您要将对象移入和移出节点,并且希望控制这些对象的哪些属性被克隆,那么这是在node.js中控制这些属性的一种很好的方法,代码也可以在浏览器中工作。
下面是它的用法示例:http://jsfiddle.net/hjchyLt1/
我认为,在没有库的情况下,缓存的重复性是最好的。
被低估的WeakMap涉及到循环的问题,其中存储对新旧对象的引用可以帮助我们很容易地重建整个树。
我阻止了DOM元素的深度克隆,可能您不想克隆整个页面:)
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
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)