如何正确克隆 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】:
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 正确调用构造函数。接受的答案没有。 适用于其他所有节点!仍然留下参考链接 递归的想法很棒。但是如果值是数组,它会工作吗?【参考方案2】:

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 有效,但如上所述不复制方法。【参考方案3】:
const objClone =  ...obj ;

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

【讨论】:

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

结构化克隆

见my answer to a near-duplicate question here。您可以使用 html 标准包含的相同结构化克隆机制在领域之间发送数据。很快您就可以通过新的structuredClone 全局方法来实现这一点。

const clone = structuredClone(original);

更多详情请见the other answer。

【讨论】:

+1 用于给出最终可能以何种形式内置的想法——即使现在无法使用。【参考方案5】:

我已经完成了上述所有解决方案,它们都很好。但是,您可以使用另一种方法来克隆对象(使用不引用的值)。 Object.assign

let x = 
    a: '1',
    b: '2'


let y = Object.assign(, x)
y.a = "3"

console.log(x)

输出将是

 a: '1', b: '2' 

此外,您还可以使用相同的方法克隆数组。

clonedArray = Object.assign([], array)

【讨论】:

【参考方案6】:

这会生成您的obj新副本(不仅仅是参考)。

let myCopy = JSON.parse(JSON.stringify(obj)); 

..比_.cloneDeep(obj) 更有效。

【讨论】:

【参考方案7】:

(以下主要是@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) 创建一个新符号吗?或者这会对众所周知的符号产生太奇怪的影响?【参考方案8】:

你可以使用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

【讨论】:

请注意,这只会执行“浅克隆”,developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…【参考方案9】:

在 JavaScript 中复制对象的方法

    使用展开 (...) 语法 使用Object.assign() 方法 使用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));

【讨论】:

扩展语法是语法糖(在这种情况下是 Object.assign)。这个答案具有误导性。 用于快速测试 Object.assign(, person);这是完美的【参考方案10】:

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

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

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

【讨论】:

是的,但取决于你想要什么(你可能想要简单地“复制”道具 valuesnot 实际的道具描述符参考...... ),并且根据源 obj,您可能还需要在此之上添加一个 Object.assign 才能获得可枚举的属性(即在对象上设置的普通旧键值对,与原型和“动态”描述的道具无关。 【参考方案11】:

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

【讨论】:

听起来你也只需要 Object.assign :)【参考方案12】:

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

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

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

【讨论】:

【参考方案13】:

性能

今天 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 用于浅大对象的示例结果

【讨论】:

【参考方案14】:

正如this link 所说,使用此代码:

let clone = Object.create(Object.getPrototypeOf(obj),
 Object.getOwnPropertyDescriptors(obj));

【讨论】:

在这里回答问题很好,但这是一个老问题,有很多很好的答案。任何新答案都应为该主题添加重要的新信息和新见解。您在这里的回答非常简短,没有任何解释,指向异地页面的链接很有用,但如果链接失败,答案应该是独立的并且有足够的详细信息。 这个和Object.assign(newObj, obj)一模一样,是浅拷贝【参考方案15】:

来自这篇文章: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); 不是一个好方法?【参考方案16】:

许多同行针对 deep_cloning 提出的解决方案JSON.parse(JSON.stringify(orig_obj) 有几个我发现的问题,如下所列:

    它在复制原始对象中值为undefined 的条目时丢弃条目, 如果有InfinityNaN等值,复制时会转换成null, 如果原始对象中有Date类型,它将在克隆对象中被字符串化(typeof date_entry --&gt; string)。

找到了一种克隆对象的有效方法,它在各种情况下都对我很有效。请看下面的代码,因为它解决了上面提到的JSON.parse(...) 的所有陷阱,但导致正确的深度克隆:

var orig_obj = 
  string: 'my_str',
  number: 123,
  bool: false,
  nul: null,
  nested : 
    value : true
  ,
  nan : NaN,
  date: new Date(), 
  undef: undefined,
  inf: Infinity,

console.log("original_obj before modification: ", orig_obj, "\n");
console.log(typeof orig_obj.date, "\n");

var clone_obj = Object.assign(, orig_obj);

//this below loop will help in deep cloning and solving above issues
for(let prop in orig_obj) 
    if(typeof orig_obj[prop] === "object") 
        if(orig_obj[prop] instanceof Date)
            clone_obj[prop] = orig_obj[prop];
        else 
            clone_obj[prop] = JSON.parse(JSON.stringify(orig_obj[prop]));
        
    


console.log("cloned_obj before modification: ", orig_obj, "\n");

clone_obj.bool = true;
clone_obj.nested.value = "false";

console.log("original_obj post modification: ", orig_obj, "\n");
console.log("cloned_obj post modification: ", clone_obj, "\n");
console.log(typeof clone_obj.date);

【讨论】:

您检查所有***属性以查看它们是否是日期,但如果它们不是,则将它们进行 JSON 字符串化/解析。如果***属性是包含 Date 作为其属性之一的对象怎么办?您的解决方案需要递归工作,以捕获所有可能无法进行 JSON.parsed 的嵌套对象的情况。【参考方案17】:

克隆对象的简单递归方法。也可以使用 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);

【讨论】:

【参考方案18】:

如果你不在你的对象中使用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,它在向工作人员发送消息或从工作人员发送消息时使用。它还包含一个深度克隆功能。

【讨论】:

有时最好的答案是最简单的。天才。【参考方案19】:
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

【讨论】:

【参考方案20】:

对象复制使用(...

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

【讨论】:

为什么 Object.assign 方式被标记为“坏”?有人可以详细说明吗?【参考方案21】:

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

【讨论】:

【参考方案22】:

简单

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
 

【讨论】:

【参考方案23】:

使用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);【参考方案24】:

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

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

_ = 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

【讨论】:

【参考方案25】:

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

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

   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: true ,而是提供了一个函数,它仍然只是复制源对象的ID attribute,但它确保它是一个数字,即使它不存在于源对象上。

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

数组可以很好地克隆,但如果您愿意,您也可以让自己的函数处理这些单独的属性,并执行以下特殊操作:

 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 属性(如果它存在)(假设它是一个数组),并且对于该数组中的每个元素,调用克隆函数来单独克隆它基于第二个模板,该模板仅复制每个标记元素的ab 属性。

如果您将对象进出节点,并且您想控制这些对象的哪些属性被克隆,那么这是在node.js 中控制它的好方法,并且代码也可以在浏览器中运行。

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

【讨论】:

【参考方案26】:

使用来自npmdeepcopy。在浏览器和node 中都可以作为npm module... 使用

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

let a = deepcopy(b)

【讨论】:

【参考方案27】:

对克隆简单对象感兴趣:

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

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

【讨论】:

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

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

【讨论】:

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

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

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

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

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,这样你会错过这些功能。 “作品”一词有多种含义。 请记住,我提到的方式,只会复制第一层的功能。因此,如果我们在彼此内部有一些对象,那么唯一的方法就是逐个字段递归地复制。【参考方案30】:

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

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

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

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

关于JavaScript对象深度克隆