我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
当前回答
我已经编写了自己的实现。不确定它是否算作更好的解决方案:
/*
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;
}
其他回答
在ES-6中,您可以简单地使用Object.assign(…)。前任:
let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);
这里有一个很好的参考:https://googlechrome.github.io/samples/object-assign-es6/
A.Levy的答案几乎是完整的,这是我的一点贡献:有一种方法可以处理递归引用,请看下面这行
如果(this[attr]==this)copy[attr]=复制;
如果对象是XMLDOM元素,则必须改用cloneNode
if(this.cloneNode)返回this.clone节点(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注释。
基于模板克隆对象。如果您不想要精确的副本,但确实想要某种可靠的克隆操作的健壮性,但您只想要克隆位,或者希望确保可以控制每个属性值的存在或格式,该怎么办?
我之所以这么做,是因为它对我们有用,我们之所以创建它,是因为我们找不到类似的东西。您可以使用它来基于模板对象克隆对象,该模板对象指定了要克隆的对象的属性,如果源对象上不存在这些属性,或者您希望如何处理克隆,则模板允许函数将这些属性转换为不同的属性。如果没有用,我相信有人可以删除这个答案。
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)
我已经在标量对象的情况下尝试过这一点,它对我很有用:
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
当做