如何正确克隆JavaScript对象?

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

如何正确克隆JavaScript对象?

小宇宙2020/03/09 13:15:41
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};
村村AL2020/03/09 13:15:41

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

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

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
凯JinJin2020/03/09 13:15:41

我只是想添加到本文中的所有Object.create解决方案中,以至于nodejs无法以所需的方式工作。

在Firefox中,结果

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

{test:"test"}

在nodejs中

{}
达蒙阿飞斯丁2020/03/09 13:15:41

对于深层复制和克隆,先JSON.stringify然后再JSON.parse该对象:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
西里神奇泡芙2020/03/09 13:15:41

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(json_original));

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

前端前端猴子2020/03/09 13:15:41

旧问题的新答案!如果您高兴地将ECMAScript 2016(ES6)与Spread Syntax一起使用,这很容易。

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

这为对象的浅表副本提供了一种干净的方法。进行深层复制(意味着要递归地嵌套每个对象中的每个值的新副本)需要上述较重的解决方案。

JavaScript不断发展。

逆天2020/03/09 13:15:41

我认为有一个简单而可行的答案。在深度复制中,有两个问题:

  1. 使属性彼此独立。
  2. 并使方法在克隆对象上保持活动状态。

因此,我认为一个简单的解决方案是首先进行序列化和反序列化,然后对其进行赋值以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

尽管这个问题有很多答案,但我希望这个问题也能有所帮助。

宝儿小哥小卤蛋2020/03/09 13:15:41
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

ES6解决方案,如果您想(浅)克隆一个类实例,而不仅仅是一个属性对象。

路易卡卡西2020/03/09 13:15:41

使用Lodash:

var y = _.clone(x, true);
樱理查德2020/03/09 13:15:41

在ECMAScript 2018中

let objClone = { ...obj };

请注意,嵌套对象仍将被复制为参考。

菏以飘落2020/03/09 13:15:40

答:Levy的答案几乎是完整的,这是我的一点贡献:有一种方法可以处理递归引用,请参见此行

if(this[attr]==this) copy[attr] = copy;

如果对象是XML DOM元素,我们必须使用cloneNode代替

if(this.cloneNode) return this.cloneNode(true);

受A.Levy详尽研究和Calvin原型设计方法的启发,我提供了以下解决方案:

Object.prototype.clone = 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;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

另请参阅答案中的Andy Burke的注释。

老丝Gil樱2020/03/09 13:15:40

一种特别优雅的解决方案是使用JSON编码来制作没有成员方法的对象的深层副本。方法是对目标对象进行JSON编码,然后通过对其进行解码,获得所需的副本。您可以根据需要解码任意数量的副本。

当然,函数不属于JSON,因此仅适用于没有成员方法的对象。

这种方法非常适合我的用例,因为我将JSON Blob存储在键值存储中,并且当它们在JavaScript API中作为对象公开时,每个对象实际上都包含该对象原始状态的副本,因此我们可以在调用者更改了暴露对象后计算增量。

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value
猿飞云斯丁2020/03/09 13:15:40

对于使用AngularJS的用户,此库中还有直接方法来克隆或扩展对象。

var destination = angular.copy(source);

要么

angular.copy(source, destination);

更多angular.copy 文档 ...

LEYJim2020/03/09 13:15:40

好的,假设您在下面有这个对象并且想要克隆它:

let obj = {a:1, b:2, c:3}; //ES6

要么

var obj = {a:1, b:2, c:3}; //ES5

答案主要取决于您使用哪种ECMAscript,在ES6+中您可以简单地使用它Object.assign来进行克隆:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

或像这样使用传播运算符:

let cloned = {...obj}; //new {a:1, b:2, c:3};

但是,如果您使用ES5,则可以使用几种方法,但是JSON.stringify,请确保不要使用要复制的大量数据,但是在许多情况下,这可能是一种方便的方式,例如:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
L西门2020/03/09 13:15:40

如果您可以使用浅表副本,那么underscore.js库提供了一个clone方法。

y = _.clone(x);

或者你可以像扩展它

copiedObject = _.extend({},originalObject);
小小小胖2020/03/09 13:15:40

使用jQuery,您可以使用extend进行浅表复制

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

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

或进行深复制

var copiedObject = jQuery.extend(true, {}, originalObject)
小哥Gil2020/03/09 13:15:40

每个MDN

  • 如果要浅拷贝,请使用 Object.assign({}, a)
  • 对于“深层”副本,请使用 JSON.parse(JSON.stringify(a))

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

西门小宇宙2020/03/09 13:15:40

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

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

但是请注意,嵌套对象仍会复制为引用。

神无Green2020/03/09 13:15:40

有很多答案,但是没有一个提到ECMAScript 5 中的Object.create,它当然不能提供确切的副本,但是会将源设置为新对象的原型。

因此,这不是对该问题的确切答案,而是单线解决方案,因此很优雅。它最适合2种情况:

  1. 此类继承有用的地方(du!)
  2. 不会修改源对象的地方,因此这两个对象之间的关系就不成问题了。

例:

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。