如何正确克隆JavaScript对象?

时间:2009-04-08 03:01:06

标签: javascript clone javascript-objects

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

如何正确克隆JavaScript对象?

73 个答案:

答案 0 :(得分:1439)

为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的日期字符串将比d2落后5秒。使一个Date与另一个setTime相同的方法是调用Date方法,但这是特定于Object类的方法。我不认为这个问题有一个防弹的一般解决方案,但我会很高兴出错!

当我不得不实施一般的深度复制时,我最后通过假设我只需要复制普通ArrayDateStringNumberBooleanObject。最后3种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设Arrayfunction 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;

只要对象和数组中的数据形成树结构,上述函数就可以适用于我提到的6种简单类型。也就是说,对象中的相同数据的引用不超过一个。例如:

{{1}}

它将无法处理任何JavaScript对象,但它可能已经足够用于许多目的,只要你不认为它只适用于你抛出的任何东西。

答案 1 :(得分:862)

如果您不在对象中使用函数,则可以使用以下非常简单的一行:

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

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

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

答案 2 :(得分:750)

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

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

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

或制作深层副本

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

答案 3 :(得分:602)

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

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

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

答案 4 :(得分:179)

MDN

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

不需要外部库,但您需要检查browser compatibility first

答案 5 :(得分:129)

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

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

  1. 这种继承有用(呃!)
  2. 不会修改源对象,从而使两个对象之间的关系成为非问题。
  3. 示例:

    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
    

    为什么我认为这个解决方案更优越?它是原生的,因此没有循环,没有递归。但是,旧版浏览器需要填充。

答案 6 :(得分:116)

在一行代码中克隆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;
    }
  });
}

答案 7 :(得分:76)

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

y = _.clone(x);

或者您可以像

一样扩展它
copiedObject = _.extend({},originalObject);

答案 8 :(得分:75)

互联网上的大多数解决方案都存在一些问题。因此,我决定进行跟进,其中包括为什么接受的答案不应被接受。

起始情况

我希望深层复制 Javascript Object及其所有孩子及其子女等等。但由于我不是一个普通的开发人员,我的Object 正常 propertiescircular structures甚至nested objects

因此,我们先创建circular structurenested object

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

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

让我们将所有内容整合到一个名为Object的{​​{1}}中。

a

接下来,我们要将var a = { x: 'a', circ: new Circ(), nested: new Nested('a') }; 复制到名为a的变量中并进行变异。

b

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

var b = a;

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

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

JSON

我尝试的第一次尝试是使用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

不要浪费太多时间,你会得到var b = JSON.parse( JSON.stringify( a ) ); b.x = 'b'; b.nested.y = 'b';

递归副本(已接受的&#34;答案&#34;)

让我们看看接受的答案。

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

此解决方案已在前一段时间添加到Javascript中,甚至可以处理var b = Object.create(a); b.x = 'b'; b.nested.y = 'b';

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" } } 的填充,就像IE 8一样。它是Mozilla推荐的东西,当然,它并不完美,导致与原生解决方案相同的问题。

Object.create

我已将function F() {}; function clonePF(o) { F.prototype = o; return new F(); } var b = clonePF(a); b.x = 'b'; b.nested.y = 'b'; 置于范围之外,因此我们可以查看F告诉我们的内容。

instanceof

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

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

在挖掘时,我发现了一个类似的问题(In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?),但有一个更好的解决方案。

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

让我们看看输出......

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 instancenested更改为circ

  

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

Object

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真实深层副本。它处理简单的 [Object] [Object] / \ / \ / \ / \ |/_ _\| |/_ _\| [Object] [Object] ===> [Object] [Object] \ / | | \ / | | _\| |/_ \|/ \|/ [Object] [Object] [Object] propertiescircular structures,但在克隆时会弄乱它们的实例。

jsfiddle

答案 9 :(得分:45)

好的,想象你下面有这个对象,你想克隆它:

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

答案 10 :(得分:38)

一个特别不优雅的解决方案是使用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

答案 11 :(得分:32)

您可以简单地使用spread property复制没有引用的对象。但要小心(请参阅注释),'copy'只是在最低的对象/数组级别。嵌套属性仍然是引用!

完成克隆:

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

答案 12 :(得分:25)

更新2020年7月6日

有三(3)种方法可以在JavaScript中克隆对象。由于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' }

希望可以将其用作参考摘要。

答案 13 :(得分:24)

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

var destination = angular.copy(source);

angular.copy(source, destination);

更多有关angular.copy documentation ...

的信息

答案 14 :(得分:22)

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在答案中的注释。

答案 15 :(得分:19)

在ES-6中,您只需使用Object.assign(...)即可。 例如:

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

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

答案 16 :(得分:19)

这是您可以使用的功能。

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

答案 17 :(得分:19)

来自本文:Brian Huisman的How to copy arrays and objects in 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;
};

答案 18 :(得分:17)

在ECMAScript 2018中

<CreateCaseMessage>

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

答案 19 :(得分:14)

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

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

答案 20 :(得分:13)

旧问题的新答案!如果您有幸使用ECMAScript 2016(ES6)和Spread Syntax,那很容易。

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

这为对象的浅拷贝提供了一种干净的方法。制作深层复制,意味着在每个递归嵌套对象中设置每个值的新副本,需要上面较重的解决方案。

JavaScript不断发展。

答案 21 :(得分:13)

性能

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

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

浅拷贝结果

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

enter image description here

深层复制的结果

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

enter image description here

详细信息

对于选定的解决方案: 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运行它
  • 深度较小:具有10个级别嵌套字段的对象-您可以HERE运行它
  • deep-big:具有1000个级别嵌套字段的对象-您可以HERE运行

测试中使用的对象显示在以下片段中

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

下面的代码段展示了经过测试的解决方案,并显示了它们之间的差异

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的示例结果

enter image description here

答案 22 :(得分:10)

使用Lodash:

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

答案 23 :(得分:10)

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

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

答案 24 :(得分:8)

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

  1. 保持彼此依赖的属性。
  2. 并使这些方法在克隆的对象上保持活动状态。

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

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

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

答案 25 :(得分:6)

JanTuroň上面的回答非常接近,由于兼容性问题,可能是最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题。例如,执行:

for ( var i in someArray ) { ... }

在遍历数组的元素之后,将clone()方法分配给i。这是一个避免枚举并适用于node.js的改编:

Object.defineProperty( Object.prototype, "clone", {
    value: 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;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

这可以避免使clone()方法可枚举,因为defineProperty()默认可枚举为false。

答案 26 :(得分:6)

我只想添加到这篇文章中的所有Object.create解决方案,这对于nodejs来说并不是以所需的方式工作。

在Firefox中的结果

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

{test:"test"}

在nodejs中,它是

{}

答案 27 :(得分: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;
}

答案 28 :(得分: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;
};

答案 29 :(得分:6)

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

答案 30 :(得分:5)

  

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

Shallow Copy: lodash _.clone()

深拷贝: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}}}

Deep Copy: lodash _.cloneDeep()

答案 31 :(得分:5)

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

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

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

答案 32 :(得分:4)

答案 33 :(得分:4)

从npm使用deepcopy。在浏览器和节点中作为npm模块工作......

https://www.npmjs.com/package/deepcopy

让a = deepcopy(b)

答案 34 :(得分:4)

请咨询http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data以获取W3C的“安全传递结构化数据”算法,该算法旨在由浏览器实施,以便将数据传递给Web工作人员。但是,它有一些限制,因为它不处理功能。有关更多信息,请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm,包括JS中的替代算法,它可以让您了解其中的一部分。

答案 35 :(得分:3)

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

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

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,console

我添加了这个例子以鼓励一些争论,所以请添加一些评论:)

答案 36 :(得分:3)

根据“模板”克隆对象。如果你不想要一个精确的副本,你会怎么做,但你确实需要某种可靠的克隆操作的健壮性,但你只想克隆位或你想确保你可以控制每个属性值的存在或格式克隆

我正在为此做出贡献,因为它对我们很有用,而且我们创建它是因为我们找不到类似的东西。您可以使用它来基于“模板”对象克隆对象,该对象指定要克隆的对象的属性,并且模板允许函数将这些属性转换为不同的属性(如果它们不存在于源对象上)或者你想要处理克隆。如果它没用,我相信有人可以删除这个答案。

   function isFunction(functionToCheck) {
       var getType = {};
       return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
   }

   function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
       if (typeof cloneConstructor === "undefined") {
           cloneConstructor = false;
       }
       if (obj == null || typeof (obj) != 'object') return obj;

       //if we have an array, work through it's contents and apply the template to each item...
       if (Array.isArray(obj)) {
           var ret = [];
           for (var i = 0; i < obj.length; i++) {
               ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
           }
           return ret;
       }

       //otherwise we have an object...
       //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
       var temp = cloneConstructor ? new obj.constructor() : {};

       for (var key in tpl) {
           //if we are provided with a function to determine the value of this property, call it...
           if (isFunction(tpl[key])) {
               temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
           } else {
               //if our object has this property...
               if (obj[key] != undefined) {
                   if (Array.isArray(obj[key])) {
                       temp[key] = [];
                       for (var i = 0; i < obj[key].length; i++) {
                           temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
                       }
                   } else {
                       temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
                   }
               }
           }
       }

       return temp;
   }

一种简单的方法就是这样:

var source = {
       a: "whatever",
       b: {
           x: "yeah",
           y: "haha"
       }
   };
   var template = {
       a: true, //we want to clone "a"
       b: {
           x: true //we want to clone "b.x" too
       }
   }; 
   var destination = cloneObjectByTemplate(source, template);

如果您想使用函数来确保返回属性或确保它是特定类型,请使用这样的模板。我们提供的函数仍然只是复制源对象的ID属性,而不是使用{ID:true},但它确保它是一个数字,即使它在源对象上不存在。

 var template = {
    ID: function (srcObj) {
        if(srcObj.ID == undefined){ return -1; }
        return parseInt(srcObj.ID.toString());
    }
}

Arrays会很好地克隆,但如果你愿意,你也可以让自己的函数处理这些单独的属性,并做一些特别的事情:

 var template = {
    tags: function (srcObj) {
        var tags = [];
        if (process.tags != undefined) {
            for (var i = 0; i < process.tags.length; i++) {

                tags.push(cloneObjectByTemplate(
                  srcObj.tags[i],
                  { a : true, b : true } //another template for each item in the array
                );
            }
        }
        return tags;
    }
 }

所以在上面,我们的模板只复制源对象的'tags'属性(如果它存在)(假设它是一个数组),并且对于该数组中的每个元素,调用clone函数来单独克隆它基于第二个模板,该模板仅复制每个标签元素的'a'和'b'属性。

如果要将对象带入和带出节点,并且想要控制克隆这些对象的哪些属性,那么这是在node.js中控制它的一种很好的方法,并且代码也可以在浏览器中工作。

以下是一个使用示例:http://jsfiddle.net/hjchyLt1/

答案 37 :(得分:3)

根据Airbnb JavaScript Style Guide ,其中包含404个贡献者:

  

在Object.assign上首选对象散布运算符,以使用浅表副本   对象。使用对象剩余运算符获取具有特定条件的新对象   属性被省略。

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

我还要警告您,即使Airbnb几乎不建议使用对象散布运算符方法。请记住,Microsoft Edge仍不支持此2018功能。

ES2016+ Compat table >>

答案 38 :(得分:3)

结构化克隆

HTML标准包括an internal structured cloning/serialization algorithm,可以创建对象的深层克隆。它仍然局限于某些内置类型,但除了JSON支持的少数类型之外,它还支持日期,RegExps,地图,集合,Blob,FileLists,ImageDatas,稀疏数组,类型化数组,以及未来可能更多。它还保留了克隆数据中的引用,允许它支持可能导致JSON错误的循环和递归结构。

Node.js支持:实验

Node.js中的v8模块当前(从节点11开始)exposes the structured serialization API directly,但此功能仍标记为“实验性”,并且在将来的版本中可能会更改或删除。如果您使用的是兼容版本,则克隆对象非常简单:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

浏览器的直接支持:可能最终?

浏览器目前不提供结构化克隆算法的直接接口,但whatwg/html#793 on GitHub中已讨论过全局structuredClone()函数。正如目前提出的那样,在大多数情况下使用它将非常简单:

const clone = structuredClone(original);

除非发布此内容,否则浏览器的结构化克隆实现仅间接公开。

异步解决方法:可用。

使用现有API创建结构化克隆的低开销方法是通过MessageChannels的一个端口发布数据。另一个端口将发出message事件,其中包含附加.data的结构化克隆。不幸的是,监听这些事件必然是异步的,并且同步替代方案不太实用。

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

使用示例:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

同步解决方法:太可怕了!

没有很好的选项可以同步创建结构化克隆。以下是一些不切实际的黑客攻击。

history.pushState()history.replaceState()都会创建第一个参数的结构化克隆,并将该值分配给history.state。您可以使用它来创建任何对象的结构化克隆,如下所示:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

使用示例:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

虽然是同步的,但这可能会非常慢。它会产生与操纵浏览器历史记录相关的所有开销。反复调用此方法可能会导致Chrome暂时无法响应。

Notification constructor创建其关联数据的结构化克隆。它还会尝试向用户显示浏览器通知,但除非您已请求通知权限,否则将以静默方式失败。如果您有其他目的的许可,我们将立即关闭我们创建的通知。

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

使用示例:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();

答案 39 :(得分:3)

(以下主要是@ Maciej Bukowski,@ A. Levy,@ Jan Turoň,@ Redu的答案和@ LeviRoberts的集成, @ RobG的评论,非常感谢!!!

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

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

功能

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

  const seen = new WeakMap()

  return (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 Object:
      case Array:
        _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
        ).call(object)

        Object.defineProperties(_object,
          Object.getOwnPropertyDescriptors(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
    }
  })(object)
}


function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const prevDesc = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }
    , copyDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      || { configurable: true, writable: true } // Custom…
      || {} // …or left to native default settings

  const { configurable: _configurable, writable: _writable } = prevDesc
    , test = _writable === undefined
      ? _configurable // Can redefine property
      : _configurable && _writable // Can assign to property

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

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

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

//测试

"use strict"
const obj0 = {

  u: undefined,

  nul: null,

  t: true,

  n: 9,

  str1: "string",

  str2: "",

  sym: Symbol("symbol"),

  [Symbol("e")]: Math.E,

  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
  },

  o: {
    n: 0,
    o: {
      f: function (...args) { }
    }
  },

  arr: [[0], [1, 2]],

  d: new Date(),

  get g() { return 0 }
}

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

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

参考

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

使用的语言技巧

  1. Conditionally add prop to object

答案 40 :(得分:2)

我在标量对象的情况下尝试了这个,它对我有用:

function binder(i) {
  return function () {
    return i;
  };
}

a=1;
b=binder(a)(); // copy value of a into b

alert(++a); // 2
alert(b); // still 1

问候。

答案 41 :(得分:2)

我认为,缓存重现是我们在没有库的情况下可以做到的最好的事情。

低估了 WeakMap 来解决周期问题,其中存储对旧对象和新对象的引用对可以帮助我们重新创建非常容易的整个树。

我阻止了DOM元素的深度克隆,可能你不想克隆整个页面:)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

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

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

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

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

        const result = obj instanceof Array ? [] : {};

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

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

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

        return result;
    }

    return copy(object);
}

一些测试:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

注意:我使用常数,for loop,=&gt;运算符和WeakMaps创建更重要的代码。今天的浏览器支持这种语法(ES6)

答案 42 :(得分:2)

简单

var restore = { name:'charlesi',
age:9}
var prev_data ={
name: 'charles'
age : 10
}

var temp = JSON.stringify(prev_data)
restore = JSON.parse(temp)

restore = {
name:'charlie',
age : 12}

输出prev_data:

{
name: 'charles'
age : 10
} 

答案 43 :(得分:1)

为支持更好地理解对象复制,this illustrative jsbin may be of value

class base {
  get under(){return true}
}

class a extends base {}

const b = {
  get b1(){return true},
  b: true
}

console.log('Object assign')
let t1 = Object.create(b)
t1.x = true
const c = Object.assign(t1, new a())
console.log(c.b1 ? 'prop value copied': 'prop value gone')
console.log(c.x ? 'assigned value copied': 'assigned value gone')
console.log(c.under ? 'inheritance ok': 'inheritance gone')
console.log(c.b1 ? 'get value unchanged' : 'get value lost')
c.b1 = false
console.log(c.b1? 'get unchanged' : 'get lost')
console.log('-----------------------------------')
console.log('Object assign  - order swopped')
t1 = Object.create(b)
t1.x = true
const d = Object.assign(new a(), t1)
console.log(d.b1 ? 'prop value copied': 'prop value gone')
console.log(d.x ? 'assigned value copied': 'assigned value gone')
console.log(d.under ? 'inheritance n/a': 'inheritance gone')
console.log(d.b1 ? 'get value copied' : 'get value lost')
d.b1 = false
console.log(d.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e = { ...t1, ...t2 }
console.log(e.b1 ? 'prop value copied': 'prop value gone')
console.log(e.x ? 'assigned value copied': 'assigned value gone')
console.log(e.under ? 'inheritance ok': 'inheritance gone')
console.log(e.b1 ? 'get value copied' : 'get value lost')
e.b1 = false
console.log(e.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator on getPrototypeOf')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) }
console.log(e1.b1 ? 'prop value copied': 'prop value gone')
console.log(e1.x ? 'assigned value copied': 'assigned value gone')
console.log(e1.under ? 'inheritance ok': 'inheritance gone')
console.log(e1.b1 ? 'get value copied' : 'get value lost')
e1.b1 = false
console.log(e1.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('keys, defineProperty, getOwnPropertyDescriptor')
f = Object.create(b)
t2 = new a()
f.x = 'a'
Object.keys(t2).forEach(key=> {
  Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))
})
console.log(f.b1 ? 'prop value copied': 'prop value gone')
console.log(f.x ? 'assigned value copied': 'assigned value gone')
console.log(f.under ? 'inheritance ok': 'inheritance gone')
console.log(f.b1 ? 'get value copied' : 'get value lost')
f.b1 = false
console.log(f.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('defineProperties, getOwnPropertyDescriptors')
let g = Object.create(b)
t2 = new a()
g.x = 'a'
Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))
console.log(g.b1 ? 'prop value copied': 'prop value gone')
console.log(g.x ? 'assigned value copied': 'assigned value gone')
console.log(g.under ? 'inheritance ok': 'inheritance gone')
console.log(g.b1 ? 'get value copied' : 'get value lost')
g.b1 = false
console.log(g.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')

答案 44 :(得分:1)

这是一个现代化的解决方案,没有Object.assign()的缺陷(不通过引用复制):

const cloneObj = (obj) => {
    return Object.keys(obj).reduce((dolly, key) => {
        dolly[key] = (obj[key].constructor === Object) ?
            cloneObj(obj[key]) :
            obj[key];
        return dolly;
    }, {});
};

答案 45 :(得分:1)

使用defaults(历史上特定于nodejs,但由于使用了现代JS,现在可从浏览器使用):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);

答案 46 :(得分:1)

如果您的对象中没有循环依赖关系,我建议您使用其他答案之一或jQuery's copy methods,因为它们看起来都非常有效。

如果存在循环依赖关系(即,两个子对象彼此链接),那么就像(从理论角度来看)no way to solve this issue elegantly一样,你有点紧张。

答案 47 :(得分:1)

使用( ...

复制对象
//bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }

//good
const originalObj = { id: 5, name: 'San Francisco'};
const copyObject = {...originalObj, pincode: 4444};
console.log(copyObject)  //{ id: 5, name: 'San Francisco', pincode: 4444 }

相同的名称可用于将复制数组从一个复制到另一个

const itemsCopy = [...items];

答案 48 :(得分:1)

好的,这可能是浅层复制的最佳选择。如果使用assign遵循许多示例,但它也保留了继承和原型。它也很简单,适用于大多数类似于Array的对象,除了具有构造函数要求或只读属性的对象。但这意味着对于TypedArrays,RegExp,Date,Maps,Sets和Object版本的原语(布尔值,字符串等),它会失败。

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

其中a可以是任何对象或类构造的实例,但对于使用专门的getter和setter或具有构造函数要求的东西再次不可靠,但对于更简单的情况,它会摇摆不定。它也适用于参数。

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

来自基本的内置对象和数组...

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

由于get / setters,构造函数必需的参数或只读属性以及对父亲的犯罪而失败。

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

答案 49 :(得分:1)

用于克隆对象的简单递归方法。也可以使用lodash.clone。

let clone = (obj) => {
	let obj2 = Array.isArray(obj) ? [] : {};
	for(let k in obj) {
          obj2[k] = (typeof obj[k] === 'object' ) ? clone(obj[k]) :  obj[k];
        }
        return obj2;
    }

let w = { name: "Apple", types: ["Fuji", "Gala"]};
let x = clone(w);
w.name = "Orange";
w.types = ["Navel"];
console.log(x);
console.log(w);

答案 50 :(得分:1)

最正确的复制对象是使用Object.create

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

这样的标记将使相同的对象具有正确的原型和隐藏的属性。

答案 51 :(得分:0)

就像this link所说的那样,请使用以下代码:
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

答案 52 :(得分:0)

对于深度复制,我使用的是:-

obj = { a: 0 , b: { c: 0}};
  let deepClone = JSON.parse(JSON.stringify(obj));
  obj.a = 5;
  obj.b.c = 5;
  console.log(JSON.stringify(obj)); // { a: 5, b: { c: 5}}
  console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

答案 53 :(得分:0)

  

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

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
  

语法

Object.assign(target, ...sources)

Object.assign()方法仅将可枚举和拥有的属性从源对象复制到目标对象。它在源上使用[[Get]],在目标上使用[[Set]],

因此它将调用getter和setter。因此,它分配属性而不是仅仅复制或定义新属性。如果合并源包含getter,则可能不适合将新属性合并到原型中。将属性定义(包括其可枚举性)复制到原型Object.getOwnPropertyDescriptor()Object.defineProperty()中。

同时复制了字符串和符号属性。

例如,在发生错误的情况下,如果属性是不可写的,则将引发TypeError,如果在引发错误之前添加了任何属性,则可以更改目标对象。

请注意,Object.assign()不会抛出nullundefined源值。

答案 54 :(得分:0)

var x = {'e': 2, 'd': 8, 'b': 5};

const y = {};
for(let key in x) {
    y[key] = x[key];
}
console.log(y); // =>>> {e: 2, d: 8, b: 5}

const z = {};
Object.keys(x).forEach(key => {
    z[key] = x[key];
});
console.log(z); // =>>> {e: 2, d: 8, b: 5}

const w = {};
for(let i = 0; i < Object.keys(x).length; i++) {
    w[Object.keys(x)[i]] = x[Object.keys(x)[i]];
}
console.log(w); // =>>> {e: 2, d: 8, b: 5}

const v = {};
for(let key of Object.keys(x)) {
    v[key] = x[key];
}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

x['q'] = 100;   // Altering x will not affect the other objects

console.log(x); // =>>> {e: 2, d: 8, b: 5, q: 100}
console.log(y); // =>>> {e: 2, d: 8, b: 5}
console.log(z); // =>>> {e: 2, d: 8, b: 5}
console.log(w); // =>>> {e: 2, d: 8, b: 5}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

答案 55 :(得分:0)

所以只是添加一些简单的东西,如果您想创建一个灯箱或类似的东西,上面的大多数答案都不必要地复杂,并且更容易简单地复制源属性和其他任何必要的东西。

答案 56 :(得分:0)

在 JavaScript 中复制对象的方法

  1. 使用展开 (...) 语法
  2. 使用 Object.assign() 方法
  3. 使用 JSON.stringify()JSON.parse() 方法
const person = {
    firstName: 'John',
    lastName: 'Doe'
};

// using spread ...
let p1 = {
    ...person
};

// using  Object.assign() method
let p2 = Object.assign({}, person);

// using JSON
let p3 = JSON.parse(JSON.stringify(person));

答案 57 :(得分:0)

你可以使用rest操作符来克隆数组或对象

let myObj = {1: 100, 'a': 200};

let clone = {...myObj}; 

clone.a = 300;

console.log(clone.a) // Output :- 300
console.log(myObj.a) // Output :- 200

答案 58 :(得分:0)

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

问题在于<element>具有parentchild属性,这些属性链接到具有parentchild值的其他元素,这些属性指向原始<element>,导致无限递归循环冗余

如果你的对象是安全和简单的东西,比如

{
    '123':456
}

...然后这里的任何其他答案都可能有用。

但如果你有......

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

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

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

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

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

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

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

                    return arrayclone;
            }

            var objectclone = {};

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

            return objectclone;
    }

    return variable;
}

答案 59 :(得分:0)

您可以在不修改父对象的情况下克隆您的对象 -

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
        for ( var property in source )
            destination[property] = source[property];
        return destination;
    } ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
        return this.extend( {}, object );
    } ) );
    /** [/Object clone]*/

    let myObj = {
        a:1, b:2, c:3, d:{
            a:1, b:2, c:3
        }
    };

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here

答案 60 :(得分:0)

如果您使用的是TypeScript,需要支持较旧的网络浏览器(因此无法使用Object.assign),并且没有使用内置克隆方法的库,您可以制作在几行代码中自己一个combine帮助器。它结合了对象,如果你只有一个,只需克隆它。

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
    const combined = {};
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;
}

答案 61 :(得分:0)

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

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

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

答案 62 :(得分:0)

我不知道哪些情况不起作用,但它让我得到了一个数组的副本。我觉得它很可爱:)希望它有所帮助

copiedArr = origArr.filter(function(x){return true})

答案 63 :(得分:0)

来自Apple JavaScript Coding Guidelines

// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
        this.x = 3;
}
innerObj.prototype.clone = function() {
    var temp = new innerObj();
    for (myvar in this) {
        // this object does not contain any objects, so
        // use the lightweight copy code.
        temp[myvar] = this[myvar];
    }
    return temp;
}

// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
        // The outer object contains an inner object.  Allocate it here.
        this.inner = new innerObj();
        this.y = 77;
}
outerObj.prototype.clone = function() {
    var temp = new outerObj();
    for (myvar in this) {
        if (this[myvar].clone) {
            // This variable contains an object with a
            // clone operator.  Call it to create a copy.
            temp[myvar] = this[myvar].clone();
        } else {
            // This variable contains a scalar value,
            // a string value, or an object with no
            // clone function.  Assign it directly.
            temp[myvar] = this[myvar];
        }
    }
    return temp;
}

// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;

// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();

// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16

史蒂夫

答案 64 :(得分:0)

要处理"无法处理的循环对象,可以引入一个名为JSOG的库,它将任意图形序列化和反序列化为JSON格式。

JSON.stringify

尝试用这个技巧修补JSOG进行克隆可能也很有趣(目前没有时间,但如果有人想试一试......):

序列化一个简单的函数:

var clone = JSOG.parse(JSOG.stringify(original));

反序列化函数:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

需要一些约定(可能在属性名称中)来识别函数与常规字符串(也许eval("foo.f = " + stringForm) )。

当然,如果函数调用第二个函数,第二个函数将需要存在,就像它对原始函数一样。

如果你要从不受信任的来源接受序列化表格,但是接受来自不受信任来源的任何形式的任何函数都是危险的,那么上面是非常危险的,所以如果你对克隆功能感兴趣,信任必须有已经建立(或者你已经打算写一个安全漏洞!)。

免责声明:我没有测试过JSOG stringify / parse与JSON stringify / parse的速度,但它确实适用于我测试它的简单(圆形)对象。

答案 65 :(得分:0)

复制最终可能指向自身的对象的问题可以通过简单的检查来解决。每次复制操作时都添加此检查。它可能,但应该工作。

我使用 toType()函数显式返回对象类型。我也有自己的 copyObj()函数,它在逻辑上非常相似,可以回答所有三个Object(),Array()和Date()案例。

我在NodeJS中运行它。

未经测试,是的。

// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
  if (toType(parent) == '[object Object]') {
    for (var name in parent) {
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
        if (isChild(target, curProperty)) return true;
      }
    }
  } else if (toType(parent) == '[object Array]') {
    for (var i=0; i < parent.length; i++) {
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
        if (isChild(target, curItem)) return true;
      }
    }
  }

  return false;     // Not the target.
}

答案 66 :(得分:0)

如果您有一个带有函数的对象,您可以使用JSONfn执行此操作,请参阅http://www.eslinstructor.net/jsonfn/

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));

答案 67 :(得分:0)

由于同样的问题,我来到了这个页面,但我既没有使用JQuery,也没有克隆方法适用于我自己的对象。

我知道我的答案与这个问题没有太大关系,因为这是一种不同的方法。我没有使用克隆函数,而是使用了create函数。它对我有用,因为以下(不幸的是限制)目的:

  1. 我主要使用JSP生成的Javascript
  2. 我知道在开始时必须生成哪个Object(在我的例子中,它是来自数据库的信息,它获取一次,需要在JS中更频繁地部署。
  3. 首先我像这样定义了我的对象:

    var obj= new Object();
    obj.Type='Row';
    obj.ID=1;
    obj.Value='Blah blah';
    

    现在我把所有东西都移动了:

    function getObjSelektor(id_nummer,selected){
    var obj = document.createElement("select");
    obj.setAttribute("id","Selektor_"+id_nummer);
    obj.setAttribute("name","Selektor");
    obj.setAttribute("size","1");
    
    var obj_opt_1 = document.createElement("option");
    obj_opt_1.setAttribute("value","1");
    if(1==selected)
        posopval_opt_1.setAttribute("selected","selected");
    obj_opt_1.innerHTML="Blah blah";
    obj.appendChild(obj_opt_1);
    
    var obj_opt_2 = document.createElement("option");
    obj_opt_2.setAttribute("value","2");
    if(2==selected)
        obj_opt_2.setAttribute("selected","selected");
    obj_opt_2.innerHTML="2nd Row";
    obj.appendChild(obj_opt_2);
    
    ...
    
    return obj;
    }
    

    并在常规代码中调用该函数:

    myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));
    

    如上所述,这是一种不同的方法,它解决了我的问题。

答案 68 :(得分:0)

//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
    this[propertyName] = definition();
}).call(
    Object,
    "clone",
    function () {
        function isfn(fn) {
            return typeof fn === "function";
        }

        function isobj(o) {
            return o === Object(o);
        }

        function isarray(o) {
            return Object.prototype.toString.call(o) === "[object Array]";
        }

        function fnclon(fn) {
            return function () {
                fn.apply(this, arguments);
            };
        }

        function owns(obj, p) {
            return obj.hasOwnProperty(p);
        }

        function isemptyobj(obj) {
            for (var p in obj) {
                return false;
            }
            return true;
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === "[object Object]";
        }
        return function (input) {
            if (isfn(input)) {
                return fnclon(input);
            } else if (isobj(input)) {
                var cloned = {};
                for (var p in input) {
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function () { return input[p].apply(input, arguments); } )
                        || ( cloned[p] = input[p] )
                    );
                }
                if (isarray(input)) {
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) {
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () {
                                return Array.prototype[methodName].apply(cloned, arguments);
                            }
                        );
                      }
                    );
                }
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
            } else {
                return input;
            }
        };
    }
));
//

答案 69 :(得分:0)

在我的代码中,我经常定义一个函数(_)来处理副本,这样我就可以“按值”传递给函数。此代码创建深层副本但保持继承。它还跟踪子副本,以便可以在没有无限循环的情况下复制自引用对象。随意使用它。

它可能不是最优雅的,但它还没有让我失望。

_ = function(oReferance) {
  var aReferances = new Array();
  var getPrototypeOf = function(oObject) {
    if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
    var oTest = new Object();
    if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
    if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
    return Object.prototype;
  };
  var recursiveCopy = function(oSource) {
    if(typeof(oSource)!=="object") return oSource;
    if(oSource===null) return null;
    for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
    var Copy = new Function();
    Copy.prototype = getPrototypeOf(oSource);
    var oCopy = new Copy();
    aReferances.push([oSource,oCopy]);
    for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
    return oCopy;
  };
  return recursiveCopy(oReferance);
};

// Examples:
Wigit = function(){};
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true

答案 70 :(得分:-1)

好的,我知道它有很多答案,但是没有人指出,EcmaScript5有分配方法,适用于FF和Chrome,它复制了可枚举和自己的属性和符号。

Object Assign

答案 71 :(得分:-2)

我最喜欢的&amp;优雅的JS对象克隆解决方案是

function CloneObject() {}
function cloneObject(o) {
   CloneObject.prototype = o;
   return new CloneObject();
}

使用cloneObject(object)获取JS对象的克隆。

与许多 copy 解决方案不同,此克隆在克隆对象中保留原型关系。

答案 72 :(得分:-4)

function clone(obj)
{
    var cloneObj = Object.create(obj);

    return cloneObj;
}

在Javascript对象中,单独继承另一个对象(Prototypal继承)。 Object.create(obj)返回一个对象,该对象是obj的子对象或子对象。在上面的函数中,它将有效地返回对象的副本。

然而,这是一种非常奇怪的克隆方式,因为我没有将继承用于其真正目的。