如何正确克隆JavaScript对象? [英] How do I correctly clone a JavaScript object?

查看:123
本文介绍了如何正确克隆JavaScript对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an object, x. I'd like to copy it as object y, such that changes to y do not modify x. I realized that copying objects derived from built-in JavaScript objects will result in extra, unwanted properties. This isn't a problem, since I'm copying one of my own, literal-constructed objects.

如何正确克隆JavaScript对象?

How do I correctly clone a JavaScript object?

推荐答案

为JavaScript中的任何对象执行此操作不会简单或直接。您将遇到错误地从对象原型中拾取属性的问题,该属性应保留在原型中而不会复制到新实例。例如,如果您要将 clone 方法添加到 Object.prototype ,如某些答案所示,您将需要显式跳过该属性。但是,如果还有其他额外的方法添加到 Object.prototype 或其他中间原型,你还不知道怎么办?在这种情况下,您将复制不应该使用的属性,因此需要使用 hasOwnProperty method。

To do this for any object in JavaScript will not be simple or straightforward. You will run into the problem of erroneously picking up attributes from the object's prototype that should be left in the prototype and not copied to the new instance. If, for instance, you are adding a clone method to Object.prototype, as some answers depict, you will need to explicitly skip that attribute. But what if there are other additional methods added to Object.prototype, or other intermediate prototypes, that you don't know about? In that case, you will copy attributes you shouldn't, so you need to detect unforeseen, non-local attributes with the hasOwnProperty method.

除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您将遇到更严峻的问题。例如, prototype 是函数的隐藏属性。此外,使用属性 __ proto __ 引用对象的原型,该属性也是隐藏的,并且不会通过迭代源对象属性的for / in循环进行复制。我认为 __ proto __ 可能是Firefox的JavaScript解释器特有的,在其他浏览器中可能会有所不同,但是你可以了解它。并非一切都是可以计算的。如果你知道它的名字,你可以复制一个隐藏的属性,但我不知道有什么方法可以自动发现它。

In addition to non-enumerable attributes, you'll encounter a tougher problem when you try to copy objects that have hidden properties. For example, prototype is a hidden property of a function. Also, an object's prototype is referenced with the attribute __proto__, which is also hidden, and will not be copied by a for/in loop iterating over the source object's attributes. I think __proto__ might be specific to Firefox's JavaScript interpreter and it may be something different in other browsers, but you get the picture. Not everything is enumerable. You can copy a hidden attribute if you know its name, but I don't know of any way to discover it automatically.

寻求优雅的又一个障碍解决方案是正确设置原型继承的问题。如果你的源对象的原型是 Object ,那么只需用 {} 创建一个新的通用对象就行了,但如果source的原型是 Object 的后代,那么你将错过使用 hasOwnProperty 过滤器,或原型中的过滤器,但首先不是可枚举的。一种解决方案可能是调用源对象的构造函数属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如, 日期 对象将其数据存储为隐藏成员:

Yet another snag in the quest for an elegant solution is the problem of setting up the prototype inheritance correctly. If your source object's prototype is Object, then simply creating a new general object with {} will work, but if the source's prototype is some descendant of Object, then you are going to be missing the additional members from that prototype which you skipped using the hasOwnProperty filter, or which were in the prototype, but weren't enumerable in the first place. One solution might be to call the source object's constructor property to get the initial copy object and then copy over the attributes, but then you still will not get non-enumerable attributes. For example, a Date object stores its data as a hidden member:

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 方法,但这是特定于日期类。我不认为这个问题有一个防弹的一般解决方案,虽然我很乐意出错!

The date string for d1 will be 5 seconds behind that of d2. A way to make one Date the same as another is by calling the setTime method, but that is specific to the Date class. I don't think there is a bullet-proof general solution to this problem, though I would be happy to be wrong!

当我不得不实施一般深度复制时最后通过假设我只需要复制一个简单的对象数组来妥协日期字符串数字布尔值。最后3种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设 Object Array 中包含的任何元素也将是该列表中的6种简单类型之一。这可以使用以下代码完成:

When I had to implement general deep copying I ended up compromising by assuming that I would only need to copy a plain Object, Array, Date, String, Number, or Boolean. The last 3 types are immutable, so I could perform a shallow copy and not worry about it changing. I further assumed that any elements contained in Object or Array would also be one of the 6 simple types in that list. This can be accomplished with code like the following:

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种简单类型,只要因为对象和数组中的数据形成树结构。也就是说,对象中的相同数据的引用不超过一个。例如:

The above function will work adequately for the 6 simple types I mentioned, as long as the data in the objects and arrays form a tree structure. That is, there isn't more than one reference to the same data in the object. For example:

// 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对象,但它可能足以用于许多目的因为你不认为它会对你投掷的任何东西起作用。

It will not be able to handle any JavaScript object, but it may be sufficient for many purposes as long as you don't assume that it will just work for anything you throw at it.

这篇关于如何正确克隆JavaScript对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆