如何正确克隆 JavaScript 对象?

IT技术 javascript clone javascript-objects
2020-12-28 23:02:03

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

如何正确克隆 JavaScript 对象?

6个回答

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

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

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是Object,那么只需创建一个新的通用对象即可{},但是如果源的原型是 的某个后代Object,那么您将丢失使用hasOwnProperty过滤器跳过的原型中的其他成员,或者在原型中,但首先是不可枚举的。一种解决方案可能是调用源对象的constructor属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,一个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将比的日期字符串晚 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 对象,但它可能足以满足许多目的,只要您不认为它只适用于您扔给它的任何东西。

这是缺少符号键和符号值。现在使用Object.getOwnPropertyDescriptors效果更好。
2021-03-05 23:02:03

如果您不在对象中使用Dates、functions、undefined、regExp 或 Infinity,一个非常简单的单行代码是JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。

另请参阅这篇关于浏览器的结构化克隆算法的文章,该算法在向工作人员发送消息和从工作人员发送消息时使用。它还包含用于深度克隆的功能。

有时最好的答案是最简单的。天才。
2021-02-06 23:02:03

使用 jQuery,您可以使用extend进行浅拷贝

var copiedObject = jQuery.extend({}, originalObject)

对 的后续更改copiedObject不会影响originalObject,反之亦然。

或者做一个深拷贝

var copiedObject = jQuery.extend(true, {}, originalObject)
将 true 指定为深拷贝的第一个参数也很有用: jQuery.extend(true, {}, originalObject);
2021-02-09 23:02:03
甚至: var copiedObject = jQuery.extend({},originalObject);
2021-02-19 23:02:03

在 ECMAScript 6 中有Object.assign方法,它将所有可枚举属性的值从一个对象复制到另一个对象。例如:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

但请注意,这是一个浅拷贝- 嵌套对象仍作为引用进行复制。

每个MDN

  • 如果你想要浅拷贝,请使用 Object.assign({}, a)
  • 对于“深”复制,请使用 JSON.parse(JSON.stringify(a))

不需要外部库,但您需要先检查浏览器兼容性