如何正确克隆 JavaScript 对象?

Posted

技术标签:

【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:

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

【问题讨论】:

看到这个问题:***.com/questions/122102/… 对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); 我真的不明白为什么没有人建议Object.create(o),它可以满足作者的所有要求? var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2; 执行此操作后,y.deep.key 也将为 2,因此 Object.create 不能用于克隆... @r3wt 这不起作用...请仅在对解决方案进行基本测试后发布.. 【参考方案1】:

对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向Object.prototype 添加clone 方法,正如某些答案所描述的那样,您将需要显式跳过该属性。但是,如果在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 的日期字符串将比 d2 的日期字符串晚 5 秒。一种使Date 与另一个相同的方法是调用setTime 方法,但这是特定于Date 类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!

当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的 ObjectArrayDateStringNumberBoolean .最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设ObjectArray 中包含的任何元素也将是该列表中的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 更好。【参考方案2】:

如果你不在你的对象中使用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()

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

另请参阅this article about the structured clone algorithm of browsers,它在向工作人员发送消息或从工作人员发送消息时使用。它还包含一个深度克隆功能。

【讨论】:

有时最好的答案是最简单的。天才。【参考方案3】:

使用jQuery,您可以浅拷贝 extend:

var copiedObject = jQuery.extend(, originalObject)

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

或者制作一个深拷贝

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

【讨论】:

甚至:var copiedObject = jQuery.extend(,originalObject); 将 true 指定为深层复制的第一个参数也很有用:jQuery.extend(true, , originalObject);【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

每MDN:

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

不需要外部库但需要查看browser compatibility first。

【讨论】:

【参考方案6】:

有很多答案,但没有一个提到 ECMAScript 5 中的Object.create,它诚然没有给你一个精确的副本,但将源设置为新对象的原型。

因此,这不是问题的确切答案,但它是一种单行解决方案,因此很优雅。它最适用于 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。

【讨论】:

这是原型继承,不是克隆。这些是完全不同的事情。新对象没有任何自己的属性,它只是指向原型的属性。克隆的重点是创建一个不引用另一个对象中任何属性的全新对象。【参考方案7】:

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

Object.assign 方法是 ECMAScript 2015 (ES6) 标准的一部分,可以满足您的需求。

var clone = Object.assign(, obj);

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

Read more...

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

【讨论】:

这只会执行浅“克隆”【参考方案8】:

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

开始情况

我想深度复制 Javascript Object 及其所有子级及其子级等等。但由于我不是一个普通的开发者,我的Object正常 propertiescircular structures 甚至nested objects

所以让我们先创建一个circular structure 和一个nested object

function Circ() 
    this.me = this;


function Nested(y) 
    this.y = y;

让我们将所有内容放在一个名为 aObject 中。

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 中的,甚至可以处理 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 有一个 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

原生解决方案相同的问题,但输出稍差。

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

在四处挖掘时,我发现了一个与此问题类似的问题 (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "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

要求匹配,但还是有一些小问题,包括将nestedcircinstance改为Object

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

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

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真实深层副本。它处理简单的propertiescircular structuresnested object,但在克隆时会弄乱它们的实例。

jsfiddle

【讨论】:

所以结论是避免这个问题:) @mikus 直到有一个 real 规范,它不仅仅涵盖基本用例,是的。 对上面提供的解决方案进行了分析,但作者得出的结论表明该问题没有解决方案。 遗憾的是 JS 不包含原生克隆功能。 在所有最热门的答案中,我觉得这是接近正确的答案。【参考方案9】:

如果你对浅拷贝没问题,underscore.js 库有一个clone 方法。

y = _.clone(x);

或者你可以像这样扩展它

copiedObject = _.extend(,originalObject);

【讨论】:

谢谢。在 Meteor 服务器上使用这种技术。 要快速开始使用 lodash,我建议学习 npm、Browserify 以及 lodash。我让克隆与 'npm i --save lodash.clone' 一起工作,然后是 'var clone = require('lodash.clone');'要让 require 工作,你需要类似 browserify 的东西。安装并了解它的工作原理后,每次运行代码时都将使用“browserify yourfile.js > bundle.js;start chrome index.html”(而不是直接进入 Chrome)。这会将您的文件和 npm 模块所需的所有文件收集到 bundle.js 中。不过,您可能可以使用 Gulp 节省时间并自动执行此步骤。【参考方案10】:

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

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,基本上更大的数据,性能更差......所以这真的取决于,而不是 kb、mb 或 gb,更多的是你想要这样做多少次。 ..它也不适用于函数和其他东西...... Object.assign 进行浅拷贝(就像传播一样,@Alizera) 你不能在 es5 中使用 let :^) @Alireza【参考方案11】:

2020 年 7 月 6 日更新

在 JavaScript 中有三 (3) 种方法可以克隆对象。由于 JavaScript 中的对象是引用值,因此不能简单地使用 = 进行复制。

方法有:

const food =  food: 'apple', drink: 'milk' 


// 1. Using the "Spread"
// ------------------

 ...food 


// 2. Using "Object.assign"
// ------------------

Object.assign(, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
//  food: 'apple', drink: 'milk' 

这可以作为参考总结。

【讨论】:

这为这个问题增加了哪些新的/独特的信息? JSON 方法将删除对象的任何方法 从一个对象创建一个字符串,然后将该字符串解析为另一个对象只是为了复制该对象是一种Monty Python的编程风格:-D 这仅适用于对象字面量和可以这样表示的对象,但不适用于 像您在 OO 语言中遇到的通用“对象”。这似乎是 OP 要求的,因此没关系,但它不是适用于每种对象的通用解决方案。 扩展运算符和 Object.assign 对于具有层次结构的对象失败,即。嵌套对象。 JSON.parse/stringify 有效,但如上所述不复制方法。【参考方案12】:

一个特别不优雅的解决方案是使用 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 格式传输... 函数不是 JSON 规范的一部分,因为它们不是一种安全(或智能)的数据传输方式,而这正是 JSON 的用途。我知道 Firefox 中的本机 JSON 编码器只是忽略传递给它的函数,但我不确定其他人的行为。 @mark: 'foo': function() return 1; 是文字构造的对象。 @abarnert 函数不是数据。 “函数字面量”用词不当——因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。【参考方案13】:

您可以简单地使用spread property 来复制没有引用的对象。但要小心(见 cmets),“副本”只是在最低的对象/数组级别。嵌套属性仍然是引用!


完整克隆:

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 实际上本身并不支持深度克隆。使用效用函数。例如拉姆达:

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

【讨论】:

这不起作用...当 x 将是一个数组时,它可能会起作用,例如 x= [ 'ab','cd',...] 这可行,但请记住这是一个 SHALLOW 副本,因此对其他对象的任何深度引用仍然是引用! 部分克隆也可以通过这种方式发生:const first = a: 'foo', b: 'bar'; const second = ...a = first【参考方案14】:
const objClone =  ...obj ;

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

【讨论】:

感谢嵌套对象仍被复制作为参考的提示!调试我的代码时我几乎发疯了,因为我修改了“克隆”上的嵌套属性,但原来的被修改了。 这是 ES2016,不是 2018,这个答案是 two years earlier。 如果我也想要嵌套属性的副本怎么办 @SunilGarg 要复制嵌套属性,您可以使用const objDeepClone = JSON.parse(JSON.stringify(obj));【参考方案15】:

对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。

var destination = angular.copy(source);

angular.copy(source, destination);

更多内容见 angular.copy documentation...

【讨论】:

这是一个深拷贝仅供参考。【参考方案16】:

来自这篇文章:How to copy arrays and objects in Javascript by Brian Huisman:

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

【讨论】:

这很接近,但不适用于任何对象。尝试用这个克隆一个 Date 对象。并非所有属性都是可枚举的,因此它们不会全部显示在 for/in 循环中。 像这样添加到对象原型对我来说破坏了 jQuery。即使我重命名为 clone2。 @iPadDeveloper2011 上面的代码有一个错误,它创建了一个名为“i”“(for i in this)”的全局变量,而不是“(for var i in this)”。我有足够的业力来编辑它并修复它,所以我做到了。 @Calvin: 这应该被创建一个不可枚举的属性,否则 'clone' 将出现在 'for' 循环中。 为什么var copiedObj = Object.create(obj); 不是一个好方法?【参考方案17】:

A.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 注释。

【讨论】:

Date.prototype.clone = function() return new Date(+this);【参考方案18】:
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;

【讨论】:

这个答案非常接近,但并不完全正确。如果您尝试克隆 Date 对象,您将不会获得相同的日期,因为对 Date 构造函数的调用会使用当前日期/时间初始化新 Date。该值不可枚举,不会被 for/in 循环复制。 并不完美,但对于那些基本情况来说很好。例如。允许对可以是基本对象、数组或字符串的参数进行简单克隆。 赞成使用 new 正确调用构造函数。接受的答案没有。 适用于其他所有节点!仍然留下参考链接 递归的想法很棒。但是如果值是数组,它会工作吗?【参考方案19】:

使用 Lodash:

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

【讨论】:

天哪,重新发明克隆技术简直太疯狂了。这是唯一理智的答案。 我更喜欢_.cloneDeep(x),因为它本质上和上面一样,但是读起来更好。【参考方案20】:

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

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

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

【讨论】:

它不会深度克隆对象。 这是一个任务,而不是副本。 clone.Title = "just a clone" 表示 obj.Title = "just a clone"。 @HoldOffHunger 你错了。在浏览器的 JS 控制台中检查 (let obj = person: 'Thor Odinson'; let clone = Object.assign(, obj); clone.title = "Whazzup";) @collapsar:这正是我检查的内容,然后 console.log(person) 将是“Whazzup”,而不是“Thor Odinson”。请参阅 August 的评论。 @HoldOffHunger 在 Chrome 60.0.3112.113 和 Edge 14.14393 中都不会发生; August 的评论不适用,因为 obj 属性的原始类型的值确实被克隆了。对象本身的属性值不会被克隆。【参考方案21】:

性能

今天 2020.04.30 我在 MacOs High Sierra v10.13.6 上对 Chrome v81.0、Safari v13.1 和 Firefox v75.0 上的所选解决方案进行了测试。

我专注于复制 DATA 的速度(具有简单类型字段的对象,而不是方法等)。解 A-I 只能做浅拷贝,解 J-U 可以做深拷贝。

浅拷贝的结果

solution ...obj (A) 在 chrome 和 firefox 上最快,在 safari 上中等速度 基于Object.assign (B) 的解决方案在所有浏览器上都很快 jQuery (E) 和 lodash (F,G,H) 解决方案中等/相当快 解决方案JSON.parse/stringify(K) 很慢 解决方案 D 和 U 在所有浏览器上都很慢

深拷贝的结果

解决方案 Q 在所有浏览器上都是最快的 jQuery (L) 和 lodash (J) 中等速度 解决方案JSON.parse/stringify(K) 很慢 解决方案 U 在所有浏览器上最慢 lodash (J) 和解决方案 U 在 Chrome 上崩溃 1000 级深度对象

详情

对于选择的解决方案: A B C(我的) D E F G H I J K L M N O P Q R S T U, 我进行了 4 次测试

浅小:具有 10 个非嵌套字段的对象 - 你可以运行它HERE 浅大:具有 1000 个非嵌套字段的对象 - 你可以运行它HERE deep-small:具有 10 个级别嵌套字段的对象 - 您可以运行它HERE deep-big: 具有 1000 个级别嵌套字段的对象 - 你可以运行它HERE

测试中用到的对象如下所示sn-p

let obj_ShallowSmall = 
  field0: false,
  field1: true,
  field2: 1,
  field3: 0,
  field4: null,
  field5: [],
  field6: ,
  field7: "text7",
  field8: "text8",


let obj_DeepSmall = 
  level0: 
   level1: 
    level2: 
     level3: 
      level4: 
       level5: 
        level6: 
         level7: 
          level8: 
           level9: [[[[[[[[[['abc']]]]]]]]]],
  ,
;

let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,);


let obj_DeepBig = genDeepObject(1000);



// ------------------
// Show objects
// ------------------

console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));




// ------------------
// HELPERS
// ------------------

function getField(k) 
  let i=k%10;
  if(i==0) return false;
  if(i==1) return true;
  if(i==2) return k;
  if(i==3) return 0;
  if(i==4) return null;
  if(i==5) return [];
  if(i==6) return ;  
  if(i>=7) return "text"+k;


function genDeepObject(N) 
  // generate: level0:level1:...levelN: end:[[[...N-times...['abc']...]]] ...
  let obj=;
  let o=obj;
  let arr = [];
  let a=arr;

  for(let i=0; i<N; i++) 
    o['level'+i]=;
    o=o['level'+i];
    let aa=[];
    a.push(aa);
    a=aa;
  

  a[0]='abc';
  o['end']=arr;
  return obj;

下面的 sn-p 展示了经过测试的解决方案并显示了它们之间的差异

function A(obj) 
  return ...obj


function B(obj) 
  return Object.assign(, obj); 


function C(obj) 
  return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), )


function D(obj) 
  let copyOfObject = ;
  Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
  return copyOfObject;


function E(obj) 
  return jQuery.extend(, obj) // shallow


function F(obj) 
  return _.clone(obj);


function G(obj) 
  return _.clone(obj,true);


function H(obj) 
  return _.extend(,obj);


function I(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;


function J(obj) 
  return _.cloneDeep(obj,true);


function K(obj) 
	return JSON.parse(JSON.stringify(obj));


function L(obj) 
  return jQuery.extend(true, , obj) // deep


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


function N(obj) 
  let EClone = function(obj) 
    var newObj = (obj instanceof Array) ? [] : ;
    for (var i in obj) 
      if (i == 'EClone') continue;
      if (obj[i] && typeof obj[i] == "object") 
        newObj[i] = EClone(obj[i]);
       else newObj[i] = obj[i]
     return newObj;
  ;

	return EClone(obj);
;

function O(obj) 
    if (obj == null || typeof obj != "object") return obj;
    if (obj.constructor != Object && obj.constructor != Array) return obj;
    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
        return new obj.constructor(obj);

    let to = new obj.constructor();

    for (var name in obj)
    
        to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];
    

    return to;


function P(obj) 
  function clone(target, source)

      for(let key in source)

          // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
          let descriptor = Object.getOwnPropertyDescriptor(source, key);
          if(descriptor.value instanceof String)
              target[key] = new String(descriptor.value);
          
          else if(descriptor.value instanceof Array)
              target[key] = clone([], descriptor.value);
          
          else if(descriptor.value instanceof Object)
              let prototype = Reflect.getPrototypeOf(descriptor.value);
              let cloneObject = clone(, descriptor.value);
              Reflect.setPrototypeOf(cloneObject, prototype);
              target[key] = cloneObject;
          
          else 
              Object.defineProperty(target, key, descriptor);
          
      
      let prototype = Reflect.getPrototypeOf(source);
      Reflect.setPrototypeOf(target, prototype);
      return target;
  
  return clone(,obj);


function Q(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] = Q(obj[i]);
        
        return copy;
    

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

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


function R(obj) 
    const gdcc = "__getDeepCircularCopy__";
    if (obj !== Object(obj)) 
        return obj; // primitive value
    

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


function S(obj) 
    const cache = new WeakMap(); // Map of old - new references

    function copy(object) 
        if (typeof object !== 'object' ||
            object === null ||
            object instanceof HTMLElement
        )
            return object; // primitive value or HTMLElement

        if (object instanceof Date) 
            return new Date().setTime(object.getTime());

        if (object instanceof RegExp) 
            return new RegExp(object.source, object.flags);

        if (cache.has(object)) 
            return cache.get(object);

        const result = object instanceof Array ? [] : ;

        cache.set(object, result); // store reference to object before the recursive starts

        if (object instanceof Array) 
            for(const o of object) 
                 result.push(copy(o));
            
            return result;
        

        const keys = Object.keys(object); 

        for (const key of keys)
            result[key] = copy(object[key]);

        return result;
    

    return copy(obj);


function T(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;


function U(obj) 
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(obj)

  function defineProp(object, key, descriptor = , copyFrom = ) 
    const  configurable: _configurable, writable: _writable 
      = Object.getOwnPropertyDescriptor(object, key)
      ||  configurable: true, writable: true 

    const test = _configurable // Can redefine property
      && (_writable === undefined || _writable) // Can assign to property

    if (!test || arguments.length <= 2) return test

    const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      ||  configurable: true, writable: true  // Custom…
      || ; // …or left to native default settings

    ["get", "set", "value", "writable", "enumerable", "configurable"]
      .forEach(attr =>
        descriptor[attr] === undefined &&
        (descriptor[attr] = basisDesc[attr])
      )

    const  get, set, value, writable, enumerable, configurable 
      = descriptor

    return Object.defineProperty(object, key, 
      enumerable, configurable, ...get || set
        ?  get, set  // Accessor descriptor
        :  value, writable  // Data descriptor
    )
  

  function clone(object) 
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) 
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) 
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        
    

    return _object
  


  function cloneObject(object) 
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key,  value: clone(object[key]) , object)
    )

    return _object
  


  function copyPropDescs(target, source) 
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  

 
// ------------------------
// Test properties
// ------------------------


console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt`)

log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)


// ------------------------
// Helper functions
// ------------------------


function deepCompare(obj1,obj2) 
  return JSON.stringify(obj1)===JSON.stringify(obj2);


function getCase()  // pure data case
  return  
    undef: undefined,
    bool: true, num: 1, str: "txt1",    
    e1: null, e2: [], e3: , e4: 0, e5: false,
    arr: [ false, 2, "txt3", null, [], ,
      [ true,4,"txt5",null, [], ,  [true,6,"txt7",null,[], ], 
        bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3: ,e4: 0, e5: false
      ],
        bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3: ,e4: 0, e5: false
    ], 
    obj:  
        bool: true, num: 12, str: "txt13",
        e1: null, e2: [], e3: , e4: 0, e5: false,
        arr: [true,14,"txt15",null,[], ],
        obj:  
          bool: true, num: 16, str: "txt17",
          e1: null, e2: [], e3: , e4: 0, e5: false,
          arr: [true,18,"txt19",null,[], ],
          obj: bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3: ,e4: 0, e5: false
       
     
  ;


function check(org, copy, field, newValue) 
  copy[field] = newValue;
  return deepCompare(org,copy); 


function testFunc(f) 
	let o =  a:1, fun: (i,j)=> i+j ;
  let c = f(o);
  
  let val = false
  try
    val = c.fun(3,4)==7;
   catch(e)  
  return val;
 

function testCirc(f) 
	function Circ() 
    this.me = this;
  

  var o = 
      x: 'a',
      circ: new Circ(),
      obj_circ: null,
  ;
  
  o.obj_circ = o;

  let val = false;

  try
    let c = f(o);  
    val = (o.obj_circ == o) && (o.circ == o.circ.me);
   catch(e)  
  return val;
 

function testRegExp(f) 
  let o = 
    re: /a[0-9]+/,
  ;
  
  let val = false;

  try
    let c = f(o);  
    val = (String(c.re) == String(/a[0-9]+/));
   catch(e)  
  return val;


function testDate(f) 
  let o = 
    date: new Date(),
  ;
  
  let val = false;

  try
    let c = f(o);  
    val = (+new Date(c.date) == +new Date(o.date));
   catch(e)  
  return val;


function testBigInt(f) 
  let val = false;
  
  try
    let o = 
      big: 123n,
    ;
  
    let c = f(o);  
  
    val = o.big == c.big;
   catch(e)  
  
  return val;


function log(f) 
  let o = getCase();  // orginal object
  let oB = getCase(); // "backup" used for shallow valid test
  
  let c1 = f(o); // copy 1 for reference
  let c2 = f(o); // copy 2 for test shallow values
  let c3 = f(o); // copy 3 for test deep values

  let is_proper_copy = deepCompare(c1,o);  // shoud be true
  
  // shallow changes
  let testShallow = 
    [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',] ]
    .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
  
  // should be true (original object shoud not have changed shallow fields)
  let is_valid = deepCompare(o,oB); 

  // deep test (intruduce some change)
  if (c3.arr[6]) c3.arr[6][7].num = 777;
  
  let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
  let diff_deep = !deepCompare(c1,c3);    // shoud be true (deep field was copied)
  let can_copy_functions = testFunc(f);
  let can_copy_circular = testCirc(f);
  let can_copy_regexp = testRegExp(f);
  let can_copy_date = testDate(f);
  let can_copy_bigInt = testBigInt(f);
  
  let has_undefined = 'undef' in c1; // field with undefined value is copied?  
  let is_ok = is_valid && is_proper_copy;
  let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
  
  testFunc(f);
  
  if(is_ok) 
    console.log(`$f.name $b(diff_shallow)   $b(diff_deep) $b(can_copy_functions) $b(can_copy_circular) $b(has_undefined)     $b(can_copy_date) $b(can_copy_regexp)  $b(can_copy_bigInt)`)
   else 
    console.log(`$f.name: INVALID $is_valid $is_proper_copy`,c1)
  
  
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>

This snippet only presents tested solutions and show differences between them (but it no make performence tests)

下面是 Chrome 用于浅大对象的示例结果

【讨论】:

【参考方案22】:

对克隆简单对象感兴趣:

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

来源:How to copy JavaScript object to new variable NOT by reference?

【讨论】:

非常好 - 简单。 @MattH:这个答案已经给了in 2012。你看见了吗? Mohammed,您在复制其中一个之前检查过现有答案吗? 这是一种方式。你从来没有想过这个【参考方案23】:

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

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 Object.create(...) 似乎绝对是要走的路。 完美答案。也许您可以为Object.hasOwnProperty 添加解释?这样人们就知道如何防止搜索原型链接。 效果很好,但是 polyfill 在哪些浏览器中工作? 这是用 obj1 作为原型创建 obj2。它之所以有效,是因为您正在遮蔽 obj2 中的 text 成员。您不是在制作副本,只是在 obj2 上找不到成员时遵循原型链。 这不会在“没有引用的情况下”创建它,它只是将引用移动到原型。它仍然是一个参考。如果原始属性发生变化,“克隆”中的原型属性也会发生变化。它根本不是克隆。【参考方案24】:
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

【讨论】:

这与let cloned = Object.assign(, obj) 有何不同? @ceztko 当obj 是类实例时,Object.assign() 不会克隆例如类方法(因为它们不可枚举)。【参考方案25】:

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

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

这为对象的浅拷贝提供了一种干净的方法。制作深层副本,即为每个递归嵌套对象中的每个值创建一个新副本,需要上述较重的解决方案。

JavaScript 不断发展。

【讨论】:

当你在对象上定义了函数时它不起作用 据我所知,传播运算符仅适用于迭代 - developer.mozilla.org 说:var obj = 'key1': 'value1'; var array = [...obj]; // TypeError: obj is not iterable @Oleh 所以使用 ` ... obj 而不是 [...obj];` @manikantgautam 我之前使用过 Object.assign(),但现在最新的 Chrome、Firefox(仍然不支持 Edge 和 Safari)确实支持对象传播语法。它的 ECMAScript 提案......但据我所知,Babel 确实支持它,所以它可能可以安全使用。【参考方案26】:

对于深拷贝和克隆,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

【讨论】:

非常聪明...这种方法有什么缺点吗?【参考方案27】:

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

    保持属性相互独立。 并在克隆对象上保持方法有效。

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

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

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

【讨论】:

虽然如果允许我导入 lodash,我更喜欢使用 lodash cloneDeep 我正在使用 JSON.parse(JSON.stringify(source))。一直在工作。 @Misha,这样你会错过这些功能。 “作品”一词有多种含义。 请记住,我提到的方式,只会复制第一层的功能。因此,如果我们在彼此内部有一些对象,那么唯一的方法就是逐个字段递归地复制。【参考方案28】:

(以下主要是@Maciej Bukowski,@A. Levy,@Jan Turoň,@Redu的回答和@LeviRoberts,@RobG的cmets的整合,非常感谢给他们!!!)

深拷贝? - 是的! (大部分);浅拷贝? - 不! (Proxy 除外)。

真诚欢迎大家测试clone(). 此外,defineProp() 旨在轻松快速地(重新)定义或复制任何类型的描述符。

功能

function clone(object) 
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) 
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) 
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) 
          //                                  // Stem from:
          case "[object Function]":
            switch (object[Symbol.toStringTag]) 
              case undefined:
                _object = cloneObject(object) // `class`
                break

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:
            _object = object                  // `Proxy`
        
    

    return _object
  


  function cloneObject(object) 
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key,  value: clone(object[key]) , object)
    )

    return _object
  



function copyPropDescs(target, source) 
  Object.defineProperties(target,
    Object.getOwnPropertyDescriptors(source)
  )



function convertFnToStr(fn) 
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr


function copyFn(fn) 
  const newFn = new Function(`return $convertFnToStr(fn)`)()
  copyPropDescs(newFn, fn)
  return newFn




function defineProp(object, key, descriptor = , copyFrom = ) 
  const  configurable: _configurable, writable: _writable 
    = Object.getOwnPropertyDescriptor(object, key)
    ||  configurable: true, writable: true 

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    ||  configurable: true, writable: true  // Custom…
    || ; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const  get, set, value, writable, enumerable, configurable 
    = descriptor

  return Object.defineProperty(object, key, 
    enumerable, configurable, ...get || set
      ?  get, set  // Accessor descriptor
      :  value, writable  // Data descriptor
  )

// 测试

const obj0 = 
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g()  return 0 ,
  o: 
    n: 0,
    o:  f: function (...args)   
  ,
  f: 
    getAccessorStr(object) 
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    ,
    f0: function f0()  ,
    f1: function ()  ,
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params)  return param => param + params ,
    f5: (a, b) => ( c = 0  = ) => a + b + c
  


defineProp(obj0, "s",  set(v)  this._s = v  )
defineProp(obj0.arr, "tint",  value:  is: "non-enumerable"  )
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0)  return a + b 
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s",  set(v)  this._s = v + 1  )
defineProp(obj1.re, "multiline",  value: true )

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = 
  f(_)  return _ ,
  func: _ => _,
  aFunc: async _ => _,
  async function()  ,
  async asyncFunc()  ,
  aFn: async function ()  ,
  *gen()  ,
  async *asyncGen()  ,
  aG1: async function* ()  ,
  aG2: async function* gen()  ,
  *[Symbol.iterator]()  yield* Object.keys(this) 


console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `$String(k):
  $fnsForTest[k].name-->
    $String(fnsForTest[k])`
).join("\n"))

const normedFnsStr = `
  f: function f(_)  return _ ,
  func: _ => _,
  aFunc: async _ => _,
  function: async function()  ,
  asyncFunc: async function asyncFunc()  ,
  aFn: async function ()  ,
  gen: function* gen()  ,
  asyncGen: async function* asyncGen()  ,
  aG1: async function* ()  ,
  aG2: async function* gen()  ,
  [Symbol.iterator]: function* ()  yield* Object.keys(this) 
`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`($normedFnsStr)`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = 
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person 
  constructor(name) 
    this.name = name
  


class Boy extends Person  
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby =  sport: "spaceflight" 

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

参考文献

    Object.create() | MDN Object.defineProperties() | MDN Enumerability and ownership of properties | MDN TypeError: cyclic object value | MDN

使用的语言技巧

    Conditionally add prop to object

【讨论】:

既然Symbol("a") === Symbol("a")false,那么clone(Symbol("a")) 不应该使用Symbol(object.description) 创建一个新符号吗?或者这会对知名符号产生太奇怪的影响?【参考方案29】:

Use lodash _.cloneDeep().

浅拷贝:lodash _.clone()

可以通过简单地复制引用来进行浅拷贝。

let obj1 = 
    a: 0,
    b: 
        c: 0,
        e: 
            f: 0
        
    
;
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//"a":4,"b":"c":4,"e":"f":100

console.log(JSON.stringify(obj3));
//"a":0,"b":"c":4,"e":"f":100

深度复制:lodash _.cloneDeep()

字段被取消引用:而不是对被复制对象的引用

let obj1 = 
    a: 0,
    b: 
        c: 0,
        e: 
            f: 0
        
    
;
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
"a":100,"b":"c":100,"e":"f":100

console.log(JSON.stringify(obj3));
"a":0,"b":"c":0,"e":"f":0

【讨论】:

【参考方案30】:

这是对 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));

【讨论】:

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

以上是关于如何正确克隆 JavaScript 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

关于JavaScript对象深度克隆