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

如何正确克隆JavaScript对象?


当前回答

复制对象最正确的方法是使用object.create:

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

这样的符号将使相同的对象具有正确的原型和隐藏的财产。

其他回答

好的,这可能是浅层复制的最佳选择。If遵循了许多使用赋值的示例,但它也保留了继承和原型。它也很简单,适用于大多数类数组和对象,但有构造函数要求或只读财产的对象除外。但这意味着它对于TypedArrays、RegExp、Date、Maps、Set和Object版本的原语(Boolean、String等)失败得很惨。

function copy ( a ) { return Object.assign( new a.constructor, a ) }

其中a可以是任何Object或类构造的实例,但对于使用专门的getter和setter或具有构造函数要求的对象来说,它同样不可靠,但对于更简单的情况来说,它很难。它也能处理争论。

您也可以将其应用于原语以获得奇怪的结果,但是。。。除非它最终成为有用的黑客,谁在乎呢。

基本内置对象和数组的结果。。。

> a = { a: 'A', b: 'B', c: 'C', d: 'D' }
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> b = copy( a )
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]

由于平均get/setter、构造函数必需的参数或只读财产,以及对父亲的冒犯,因此失败。

> a = /\w+/g
/\w+/g
> b = copy( a )  // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a )  // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a )  // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a )  // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a )  // fails because of of sins against the father
{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' } 

有很多答案,但没有一个提到ECMAScript 5中的Object.create,它确实没有给您一个确切的副本,而是将源设置为新对象的原型。

因此,这不是问题的确切答案,但它是一个单行解决方案,因此很优雅。它最适合2种情况:

在这种继承有用的地方(duh!)源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这种解决方案更优越?它是本地的,因此没有循环,没有递归。然而,较旧的浏览器将需要polyfill。

本文来自Brian Huisman的《如何在Javascript中复制数组和对象》:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

我为这个问题提供了答案,因为我在这里没有看到任何解决DOM元素问题的本地递归实现。

问题是<element>具有父属性和子属性,这些属性链接到具有父值和子值的其他元素,这些元素指向原始<element>,从而导致无限递归或循环冗余。

如果你的目标是安全简单的

{
    '123':456
}

……那么这里的任何其他答案都可能奏效。

但如果你有。。。

{
    '123':<reactJSComponent>,
    '456':document.createElement('div'),
}

…那么你需要这样的东西:

    // cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) {
    const variable = args.variable;

    if(variable === null) {
            return null;
    }

    if(typeof(variable) === 'object') {
            if(variable instanceof HTMLElement || variable.nodeType > 0) {
                    return null;
            }

            if(Array.isArray(variable)) {
                    var arrayclone = [];

                    variable.forEach((element) => {
                            arrayclone.push(cloneVariable({'variable':element}));
                    });

                    return arrayclone;
            }

            var objectclone = {};

            Object.keys(variable).forEach((field) => {
                    objectclone[field] = cloneVariable({'variable':variable[field]});
            });

            return objectclone;
    }

    return variable;
}

您可以克隆一个对象,并使用一行代码从上一个对象中删除任何引用。只需执行以下操作:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

对于当前不支持Object.create的浏览器/引擎,可以使用以下polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}