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

如何正确克隆JavaScript对象?


当前回答

由于mindeeavor指出要克隆的对象是一个“字面构造的”对象,因此解决方案可能是简单地多次生成对象,而不是克隆对象的实例:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();

其他回答

使用默认值(历史上特定于nodejs,但由于现代JS,现在可以从浏览器中使用):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);

根据MDN:

如果要浅层复制,请使用Object.assign({},a)对于“深度”复制,请使用JSON.parse(JSON.stringify(a))

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

您可以使用函数闭包来获得深度复制的所有好处,而无需深度复制。这是一个非常不同的范例,但效果很好。不要试图复制现有对象,只要在需要时使用函数实例化新对象即可。

首先,创建一个返回对象的函数

function template() {
  return {
    values: [1, 2, 3],
    nest: {x: {a: "a", b: "b"}, y: 100}
  };
}

然后创建一个简单的浅层复制函数

function copy(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
}

创建一个新对象,并将模板的财产复制到其中

var newObject = {}; 
copy(newObject, template());

但上述复制步骤不是必需的。您需要做的就是:

var newObject = template();

现在您有了一个新对象,请测试它的财产:

console.log(Object.keys(newObject));

这将显示:

["values", "nest"]

是的,这些是newObject自己的财产,而不是对另一个对象上财产的引用。让我们检查一下:

console.log(newObject.nest.x.b);

这将显示:

"b"

newObject获得了模板对象的所有财产,但没有任何依赖链。

http://jsbin.com/ISUTIpoC/1/edit?js安慰

我添加此示例是为了鼓励一些讨论,因此请添加一些评论:)

互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,包括为什么不应该接受接受的答案。

启动情况

我想深度复制一个Javascript对象及其所有子对象和它们的子对象等等。但由于我不是一个普通的开发人员,我的对象具有普通的财产、循环结构甚至嵌套对象。

因此,让我们先创建一个圆形结构和一个嵌套对象。

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

让我们把所有的东西放在一个名为a的对象中。

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

接下来,我们要将a复制到名为b的变量中,并对其进行变异。

var b = a;

b.x = 'b';
b.nested.y = 'b';

你知道这里发生了什么,因为如果不是这样,你甚至不会回答这个伟大的问题。

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

现在让我们找到一个解决方案。

JSON

我第一次尝试是使用JSON。

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

不要在上面浪费太多时间,你会得到TypeError:将循环结构转换为JSON。

递归副本(接受的“答案”)

让我们来看看公认的答案。

function cloneSO(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

看起来不错,呵呵?它是对象的递归副本,也处理其他类型,如Date,但这不是必需的。

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

递归和循环结构不能很好地一起工作。。。RangeError:超出了最大调用堆栈大小

本地解决方案

在与同事争吵后,老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫做Object.create。

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

这个解决方案不久前被添加到Javascript中,甚至可以处理循环结构。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

…你看,它不适用于内部的嵌套结构。

本地解决方案的polyfill

在旧浏览器中有一个用于Object.create的polyfill,就像IE 8一样。它有点像Mozilla推荐的,当然,它不是完美的,并且会导致与本机解决方案相同的问题。

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

我把F放在了范围之外,这样我们可以看看instanceof告诉我们什么。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

问题与本机解决方案相同,但输出稍差。

更好(但不是完美)的解决方案

当我仔细研究时,我发现了一个与这个问题类似的问题(在Javascript中,当执行深度复制时,我如何避免循环,因为属性是“this”?),但有一个更好的解决方案。

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

让我们看看输出。。。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

需求是匹配的,但仍有一些较小的问题,包括将nested和circ的实例更改为Object。

共享一片叶子的树的结构不会被复制,它们将变成两片独立的叶子:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

结论

最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真正深度副本。它处理简单的财产、循环结构和嵌套对象,但在克隆时会弄乱它们的实例。

小提琴演奏家

如果您的对象是类(例如。https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes):

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

然后在copiedObject中,您有一个originalObject类及其所有方法的深度复制实例。