如何正确克隆JavaScript对象?


3075

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

如何正确克隆JavaScript对象?



256
对于JSON,我使用mObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh。

67
我真的不明白为什么没人建议Object.create(o),它能满足作者的所有要求吗?
froginvasion 2014年

43
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; 执行此操作后,y.deep.key也将是2,因此无法使用Object.create进行克隆...
Ruben Stolk

17
@ r3wt将不起作用...请仅在对该解决方案进行基本测试之后才能发布
。–

Answers:


1560

要对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该类。尽管我很乐意弄错,但我认为没有解决该问题的通用解决方案!

当我不得不实施一般深拷贝我最终通过假设我只需要复制一个普通的妥协ObjectArrayDateStringNumber,或Boolean。后三种类型是不可变的,因此我可以执行浅表复制,而不必担心它会更改。我进一步假定包含在其中的任何元素,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对象,但是只要您不认为它可以对您扔给它的任何东西起作用,它就可以满足许多目的。


5
在nodejs中几乎可以正常工作-只需更改(var i = 0,var len = obj.length; i <len; ++ i){为for(var i = 0; i <obj.length; ++ i){
Trindaz

5
对于未来的Google员工:相同的深层副本,在gist.github.com/2234277
Trindaz'3

7
如今JSON.parse(JSON.stringify([some object]),[some revirer function])是解决方案吗?
KooiInc 2012年

5
在第一个代码段中,您确定不应该这样做var cpy = new obj.constructor()吗?
cyon

8
对象分配似乎无法在Chrome中进行真正的复制,而是保留了对原始对象的引用-最终使用JSON.stringify和JSON.parse进行了克隆-效果很好
1owk3y

974

如果您Date在对象内未使用s,函数,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()

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

另请参阅本文有关浏览器的结构化克隆算法的文章,该算法在与工作人员之间发布消息时使用。它还包含用于深度克隆的功能。


42
请注意,这只能用于测试。首先,就时间和内存消耗而言,这远非最佳。其次,并非所有浏览器都具有此方法。
Nux

2
您可以始终包含JSON2.js或JSON3.js。无论如何,您将需要它们用于您的应用程序。但我同意这可能不是最佳解决方案,因为JSON.stringify不包含继承的属性。
蒂姆·洪

78
@Nux,为什么在时间和内存方面不是最优的?MiJyn说:“此方法比(在深层对象上)浅复制慢的原因是,根据定义,该方法是深层复制。但是,由于JSON是在本机代码中实现的(在大多数浏览器中),因此它的速度要快得多比使用任何其他基于javascript的深度复制解决方案要好,有时可能比基于javascript的浅层复制技术要快(请参阅:jsperf.com/cloning-an-object/79)。” stackoverflow.com/questions/122102/...
BeauCielBleu

16
我只想为此添加2014年10月的更新。Chrome37+使用JSON.parse(JSON.stringify(oldObject))更快。使用此功能的好处是,如果需要,JavaScript引擎很容易看到并优化为更好的东西。
mirhagk 2014年

17
2016年更新:现在应该可以在几乎所有被广泛使用的浏览器中使用。(请参阅“我可以使用...”。)现在的主要问题是性能是否足够。
詹姆斯·福斯特

783

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

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

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

或进行深复制

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

164
甚至:var copiedObject = jQuery.extend({},originalObject);
格兰特·麦克莱恩

82
将true指定为深度复制的第一个参数也很有用:jQuery.extend(true, {}, originalObject);
Will Shaver

6
是的,我发现这个链接有用的(相同的解决方案帕斯卡)stackoverflow.com/questions/122102/...
加里英语

3
请注意,这不会复制原始对象的原型构造函数
Sam Jones

1
根据stackoverflow.com/questions/184710/…,似乎“浅表复制”只是复制了原始Object的引用,所以为什么在这里这么说...subsequent changes to the copiedObject will not affect the originalObject, and vice versa...。对不起,我真的很困惑。
卡尔

684

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

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

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


是的,我相信这Object.assign是要走的路。填充

22
还应注意,这将复制通过对象文字定义的“方法”(因为它们可枚举的),而不是通过“类”机制定义的方法(因为这些是不可枚举的)。
马库斯·朱尼乌斯·布鲁图斯

16
我认为应该提到的是,除了Edge之外,IE并没有对此提供支持。有些人仍然使用它。
Saulius

1
这与@EugeneTiurin的答案相同。
威尔特

5
从这里的经验来讲...请勿使用。对象被设计为分层的,您将仅复制第一级,从而导致您分配已复制的整个对象。相信我,这可以节省您数天的抓挠时间。
ow3n19年

232

每个MDN

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

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


8
JSON.parse(JSON.stringify(a))看上去很漂亮,但是在使用它之前,我建议对所需集合的时间进行基准测试。根据对象的大小,这可能不是最快的选择。
Edza

3
我注意到的JSON方法可将日期对象转换为字符串,但不能转换回日期。必须处理Javascript中的时区乐趣,并手动修复任何日期。可能对其他类型的类似案件,除了日期
史蒂夫西格

对于Date,我将使用moment.js,因为它具有clone功能。在此处查看更多信息momentjs.com/docs/#/parsing/moment-clone
Tareq

很好,但是请注意对象是否具有内部功能
lesolorzanov

json解析的另一个问题是循环引用。如果对象内有任何循环引用,则将中断
philoj '19

133

有很多答案,但是没有一个提到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。


注意:Object.create不是深层副本(itpastorn提到没有递归)。证明:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts 2013年

103
这是原型继承,而不是克隆。这些是完全不同的东西。新对象没有任何其自身的属性,只是指向原型的属性。克隆的重点是创建一个新的新对象,该对象不引用另一个对象中的任何属性。
2014年

7
我完全赞成你。我也同意,这并非像“预期的”那样克隆。但是,来吧,请拥抱JavaScript的本质,而不是尝试查找未标准化的晦涩解决方案。当然,您不喜欢原型,它们对您都是“坏”,但实际上,如果您知道自己在做什么,它们将非常有用。
froginvasion 2014年

4
@RobG:本文介绍了引用和克隆之间的区别:en.wikipedia.org/wiki/Cloning_(programming)Object.create通过引用指向父级的属性。这意味着,如果父级的属性值更改,则子级的属性值也会更改。嵌套数组和对象有一些令人惊讶的副作用,如果您不了解它们,可能会导致代码中难以发现的错误:jsbin.com/EKivInO/2。克隆对象是一个全新的独立对象,具有与父对象相同的属性和值,但未连接到父对象。
2014年

1
这会误导人们... Object.create()可以用作继承的手段,但克隆离它很近。
prajnavantha

128

在一行代码中克隆Javascript对象的一种优雅方法

一种Object.assign方法是在2015年的ECMAScript(ES6)标准的一部分,不正是你需要的。

var clone = Object.assign({}, obj);

Object.assign()方法用于将所有可枚举的自身属性的值从一个或多个源对象复制到目标对象。

阅读更多...

支持旧版浏览器的polyfill

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

抱歉,您的问题很愚蠢,但是Object.assignvaluepolyfill中的函数仅接受一个参数时,为什么要接受两个参数?
Qwertie '16

@Qwertie昨天所有参数都被迭代并合并到一个对象中,优先考虑上次传入的arg的属性
Eugene Tiurin

哦,我知道了,谢谢(我以前并不熟悉该arguments对象。)我在Object()通过Google 查找时遇到了麻烦...这是一种类型转换,不是吗?
Qwertie

44
这只会执行一个浅表的“克隆”
Marcus Junius Brutus

1
这个答案与此完全相同:stackoverflow.com/questions/122102/…我知道是同一个人,但是您应该参考而不是仅仅复制答案。
lesolorzanov

86

互联网上的大多数解决方案都有几个问题。因此,我决定进行跟进,包括为什么不接受接受的答案。

起始情况

我想复制Object包含所有子代及其子代的Javascript 。但是由于我不是一个普通的开发人员,所以我Object拥有正常的 propertiescircular structures甚至nested objects

因此,让我们创建一个circular structurenested object第一个。

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

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

让我们将所有内容放在一起Object命名为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: Converting circular structure to 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';

递归并且circular structures不能很好地协同工作...RangeError: Maximum call stack size exceeded

本机解决方案

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

var b = Object.create(a);

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

该解决方案是在一段时间之前添加到Javascript甚至是handles的circular structure

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就像IE 8一样,在较旧的浏览器中也有一个polyfill 。这有点像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

要求相匹配,但仍有一些小问题,包括不断变化instancenestedcircObject

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

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

结论

最后一种使用递归和缓存的解决方案可能不是最好的,但它是对象的真正的深层复制。它操作简单propertiescircular structures并且nested object,却会同时克隆弄乱它们的实例。

jsfiddle


11
所以讨论是为了避免这个问题:)
mikus 2014年

@mikus,直到有一个真正的规范涵盖了不止基本的用例,是的。
Fabio Poloni 2014年

2
对上面提供的解决方案进行了很好的分析,但作者得出的结论表明,该问题没有解决方案。
阿米尔·莫格

2
JS不包含本地克隆功能真是可惜。
l00k

1
在所有最重要的答案中,我认为这接近正确的答案。
KTU

77

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

y = _.clone(x);

或者你可以像扩展它

copiedObject = _.extend({},originalObject);

2
谢谢。在Meteor服务器上使用此技术。
涡轮增压

为了快速开始使用lodash,我建议您学习npm,Browserify和lodash。我将克隆与'npm i --save lodash.clone'一起使用,然后与'var clone = require('lodash.clone');'一起使用。要要求工作,您需要诸如browserify之类的东西。安装并了解其工作原理后,每次运行代码时(而不是直接进入Chrome),都将使用“ browserify yourfile.js> bundle.js; start chrome index.html”。这会将您的文件和所有需要的文件从npm模块收集到bundle.js中。不过,您可能可以节省时间并使用Gulp自动执行此步骤。
亚伦·贝尔

64

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

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

您能举个例子说明什么big chunk of data吗?100kb?100MB?谢谢!
user1063287

是的,@ user1063287,基本上是更大的数据,性能更差...所以它实际上取决于,而不是kb,mb或gb,更多的是您还想执行多少次...也将无法正常工作的功能和其他东西...
Alireza

3
Object.assign进行浅表复制(就像传播一样,@ Alizera)
Bogdan D

您不能在es5中使用let:^)@Alireza
SensationSama

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

为什么函数不属于JSON?我看过一次他们将它们作为JSON传输了更多……
the_drow

5
函数不是JSON规范的一部分,因为它们不是传输数据的安全(或灵巧)方式,而这正是JSON的目的。我知道Firefox中的本机JSON编码器只是忽略传递给它的函数,但是我不确定其他人的行为。
克里斯·沃克

1
@mark:{ 'foo': function() { return 1; } }是一个文字构造的对象。
abarnert

@abarnert函数不是数据。“函数字面量”用词不当-因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。
vemv

35

您可以简单地使用spread属性复制没有引用的对象。但是要小心(请参阅注释),“副本”仅位于最低的对象/数组级别。嵌套属性仍然是参考!


完整克隆:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

克隆二级引用:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript实际上实际上不支持深度克隆。使用实用程序功能。例如Ramda:

http://ramdajs.com/docs/#clone


1
这不起作用...当x将是一个数组时可能起作用,例如x = ['ab','cd',...]
KamilKiełczewski16年

3
这是可行的,但请记住,这是一个SHALLOW副本,因此对其他对象的任何深层引用都将保留为引用!
Bugs Bunny

也可以通过这种方式进行部分克隆:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
CristianTraìna18年

25

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

var destination = angular.copy(source);

要么

angular.copy(source, destination);

更多angular.copy 文档 ...


2
这是FYI的深层副本。
zamnuts 2014年

22

这是您可以使用的功能。

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
这个答案很接近,但不是很正确。如果尝试克隆Date对象,则不会获得相同的日期,因为对Date构造函数的调用将使用当前日期/时间初始化新的Date。该值不可枚举,不会被for / in循环复制。
A. Levy,

不是完美的,但对于那些基本情况而言却不错。例如,允许简单克隆参数,该参数可以是基本的Object,Array或String。
james_womack 2013年

赞成使用正确调用构造函数new。接受的答案不是。
GetFree

在节点上的其他一切工作!仍保留参考链接
user956584 '17

递归的想法很棒。但是如果值是array,它将起作用吗?
Q10Viking

22

答: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的注释。


3
Date.prototype.clone = function() {return new Date(+this)};
RobG 2014年

22

摘自本文:如何用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;
};

4
这很接近,但不适用于任何对象。尝试以此克隆一个Date对象。并非所有属性都是可枚举的,因此它们不会全部出现在for / in循环中。
A. Levy,

像这样添加到对象原型对我来说破坏了jQuery。即使我重命名为clone2。
iPadDeveloper2011

3
@ iPadDeveloper2011上面的代码存在一个错误,即它创建了一个名为'i''(for this in for)而不是'(for var i this')的全局变量。我有足够的业力来编辑和修复它,所以我做到了。
mikemaccana 2012年

1
@Calvin:应将其创建为不可枚举的属性,否则“克隆”将出现在“ for”循环中。
mikemaccana 2012年

2
为什么不是var copiedObj = Object.create(obj);一个好方法呢?
Dan P.

19

在ES-6中,您可以简单地使用Object.assign(...)。例如:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

一个很好的参考在这里:https : //googlechrome.github.io/samples/object-assign-es6/


12
它不会深度克隆对象。
8

那是一项作业,而不是副本。clone.Title =“只是一个克隆”意味着obj.Title =“只是一个克隆”。
HoldOffHunger

@HoldOffHunger您弄错了。检查它在浏览器的JS控制台(let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";
坍缩

@collapsar:正是我检查过的内容,然后console.log(person)将是“ Whazzup”,而不是“ Thor Odinson”。参见August的评论。
HoldOffHunger

1
@HoldOffHunger在Chrome 60.0.3112.113和Edge 14.14393中均不会发生;obj由于确实克隆了属性的原始类型的值,因此August的注释不适用。对象本身的属性值将不会被克隆。
collapsar

18

在ECMAScript 2018中

let objClone = { ...obj };

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


1
感谢您暗示仍会复制嵌套对象作为参考!调试代码时,我几乎疯了,因为我修改了“克隆”上的嵌套属性,但原来的却被修改了。
Benny Neugebauer

这是ES2016,而不是2018,而这个答案早在两年前就给出
Dan Dascalescu

那么,如果我还想复制嵌套属性怎么办
Sunil Garg

1
@SunilGarg也可以复制嵌套属性,您可以使用 const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

使用Lodash:

var y = _.clone(x, true);

5
天哪,重新发明克隆太疯狂了。这是唯一理智的答案。
丹·罗斯

5
我更喜欢,_.cloneDeep(x)因为它本质上与上述相同,但阅读效果更好。
garbanzio


13

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

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();
    };
}

1
+1 Object.create(...)似乎肯定是要走的路。
勒内Nyffenegger

完美的答案。也许您可以添加一个解释Object.hasOwnProperty?这样,人们就知道如何防止搜索原型链接。
froginvasion 2014年

工作正常,但polyfill可在哪些浏览器中工作?
伊恩·伦恩

11
这是使用obj1作为原型创建obj2。这仅适用于您text在obj2 中隐藏该成员。您不是在制作副本,只是在obj2上找不到成员时顺应原型链。
Nick Desaulniers 2014年

2
这不会“没有引用”创建它,只是将引用移至原型。仍然是参考。如果原始属性发生变化,则“克隆”中的原型属性也会发生变化。根本不是克隆。
Jimbo Jonny,

13

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

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

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

JavaScript不断发展。


2
当您在对象上定义了函数时,它不起作用
Petr Marek

据我看到的蔓延运营商只能用iterables工程- developer.mozilla.org说: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
奥莱

@Oleh,所以使用`{... obj}代替[... obj];`
manikant gautam

@manikantgautam我以前使用过Object.assign(),但是现在确实确实在最新的Chrome,Firefox(仍然不在Edge和Safari中)支持对象传播语法。它的ECMAScript提议...但是据我所知,Babel确实支持它,因此它可以安全使用。
Oleh

12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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


这有何不同let cloned = Object.assign({}, obj)
ceztko

10

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

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

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

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

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


尽管如果允许导入lodash,我还是更喜欢使用lodash cloneDeep
ConductedClever

2
我正在使用JSON.parse(JSON.stringify(source))。一直在工作。
Misha

2
@Misha,这样您将错过所有功能。术语“作品”具有许多含义。
ConductedClever

并请记住,按照我前面提到的方式,仅复制第一层的功能。因此,如果我们彼此之间有一些对象,那么唯一的方法是递归地逐字段复制。
ConductedClever

9

对于深层复制和克隆,先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}}

相当聪明...这种方法有什么缺点?
Aleks

8

这是对A. Levy的代码的一种改编,它也可以处理函数和多个/循环引用的克隆-这意味着,如果要克隆的树中的两个属性是同一对象的引用,则克隆的对象树将具有这些属性指向引用对象的一个​​克隆。这也解决了循环依赖的情况,如果不加以处理,则会导致无限循环。该算法的复杂度为O(n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


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



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

一些快速测试

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

1
截至2016年9月,这是该问题的唯一正确解决方案。
DomQ

6

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

在Firefox中,

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

{test:"test"}

在nodejs中

{}

这是原型继承,而不是克隆。
2014年

1
@ d13当您的参数有效时,请注意,JavaScript中没有标准化的方法来克隆对象。这是典型的继承,但是如果您了解概念,仍可以将其用作克隆。
froginvasion 2014年

@froginvasion。使用Object.create的唯一问题是嵌套对象和数组只是对原型嵌套对象和数组的指针引用。jsbin.com/EKivInO/2/edit?js,console。从技术上讲,“克隆”对象应具有自己的唯一属性,这些属性不是对其他对象属性的共享引用。
2014年

@ d13好的,我现在明白了。但是我的意思是,太多的人对原型继承的概念感到陌生,对我而言,我无法了解它的工作原理。如果我没记错的话,可以通过调用Object.hasOwnProperty检查您是否拥有该数组来解决您的示例。是的,这确实增加了额外的复杂性来处理原型继承。
froginvasion 2014年

6
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;
};

2
if(!src && typeof src != "object"){。我认为应该||不会&&
MikeM 2013年

6

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

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

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

6

我已经编写了自己的实现。不确定是否算是更好的解决方案:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

以下是实现:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}

尽管我的情况有点复杂,但不能为我的目标工作。
Sajuuk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.