我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。

如何正确克隆JavaScript对象?


当前回答

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。

其他回答

我已经编写了自己的实现。不确定它是否算作更好的解决方案:

/*
    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;
}

您可以简单地使用扩展属性来复制没有引用的对象。但是要小心(请参见注释),“副本”仅位于最低的对象/数组级别。嵌套的财产仍然是引用!


完整克隆:

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:

http://ramdajs.com/docs/#clone

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(JSON_inoriginal));

来源:如何通过引用将JavaScript对象复制到新变量NOT?

2022年更新

有一个新的JS标准叫做结构化克隆。它可以在许多浏览器中工作(请参阅我可以使用)。

const clone = structuredClone(object);

旧答案

对JavaScript中的任何对象执行此操作都不简单或直接。您将遇到错误地从对象的原型中提取属性的问题,这些属性应该留在原型中,而不是复制到新实例中。例如,如果您要向Object.prototype添加一个克隆方法,如某些答案所示,则需要显式跳过该属性。但是,如果有其他添加到Object.prototype或其他中间原型的方法,您不知道,该怎么办?在这种情况下,您将复制不应该复制的属性,因此需要使用hasOwnProperty方法检测不可预见的非本地属性。

除了非枚举属性之外,当您尝试复制具有隐藏财产的对象时,还会遇到更困难的问题。例如,原型是函数的隐藏属性。此外,对象的原型被__proto__属性引用,该属性也是隐藏的,不会被for/in循环复制到源对象的属性上。我认为__proto__可能特定于Firefox的JavaScript解释器,在其他浏览器中可能会有所不同,但你可以理解这一点。并非所有事物都是可枚举的。如果知道隐藏属性的名称,可以复制它,但我不知道有什么方法可以自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果源对象的原型是object,那么只需使用{}创建一个新的通用对象就可以了,但是如果源对象原型是object的后代,那么您将丢失该原型中使用hasOwnProperty筛选器跳过的其他成员,或者这些成员在原型中,但最初不可枚举。一种解决方案可能是调用源对象的构造函数属性以获取初始复制对象,然后复制属性,但这样仍然无法获取不可枚举的属性。例如,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。最后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,也没有一个克隆方法适用于我自己的对象。

我知道我的答案与这个问题没有太大关系,因为这是一种不同的方法。我不使用克隆函数,而是使用创建函数。它对我来说有以下(不幸的是限制)目的:

我主要使用JSP生成的Javascript我一开始就知道必须生成哪个Object(在我的例子中,它是来自数据库的信息,只获取一次,需要在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));

正如所说,这是一种不同的方法,它为我的目的解决了我的问题。