如何正确克隆一个JavaScript对象?

3720
我有一个对象 x。我想将它作为对象 y 复制,使得对 y 的更改不会修改 x。我意识到,复制从内置 JavaScript 对象派生的对象会导致额外的、不必要的属性。这不是问题,因为我要复制的是我自己通过字面量构造的对象。
如何正确地克隆 JavaScript 对象?

34
看这个问题:https://dev59.com/83VD5IYBdhLWcg3wAGiD这个问题是关于如何克隆JavaScript对象的,你需要找到一种高效的方法来实现这个目标。 - Niyaz
289
对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); - Lord Loh.
78
我不明白为什么没有人建议使用 Object.create(o),它完全满足了作者的要求。 - froginvasion
59
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; 执行完这段代码后,y.deep.key 的值也会变成2,因此 Object.create 不能用于克隆对象。 - Ruben Stolk
23
@r3wt那样做行不通...请先对解决方案进行基本测试再发帖。 - user3275211
显示剩余21条评论
82个回答

61

2020年7月6日更新

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

这可以用作参考摘要。


10
JSON 方法会移除对象中的所有方法。 - Andreas
3
从一个对象创建一个字符串,然后将该字符串解析为另一个对象,只是为了复制该对象,这是一种蒙提·派森式的编程风格 :-D - Jaromír Adamec
1
这仅适用于对象字面量和可以表示为此类字面量的对象,但不适用于像您在OO语言中遇到的通用“对象”。 这似乎是OP所要求的,因此这是可以的,但它不是每种对象的通用解决方案。 - bart
3
扩展运算符和Object.assign在具有层次结构(即嵌套对象)的对象中失败。JSON.parse/stringify可以使用,但如上所述,它不会复制方法。 - iCode
展开语法是语法糖(在这种情况下相当于Object.assign)。这个答案是误导性的。 - Tchakabam
显示剩余2条评论

47

一种特别不优雅的解决方案是使用 JSON 编码来创建没有成员方法的对象的深层副本。该方法是将目标对象转换为 JSON 编码,然后通过解码它,你可以得到所需的副本。你可以解码多次以获得所需的多个副本。

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

这种方法非常适合我的用例,因为我在键值存储中存储了 JSON 数据块,在 JavaScript API 中将其公开为对象时,每个对象实际上都包含原始对象状态的副本,以便我们在调用方修改公开对象后计算差异。

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

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

object2.key = "a change";
console.log(object1);// returns value

为什么函数不属于JSON?我曾经看到它们被转换为JSON超过一次... - the_drow
6
函数不是 JSON 规范的一部分,因为它们不是传输数据的安全(或明智)方式,这正是 JSON 的设计初衷。我知道 Firefox 中本地的 JSON 编码器会简单地忽略传递给它的函数,但其他编码器的行为我就不确定了。 - Kris Walker
1
@标记:{'foo': function() {return 1;}} 是一个字面构造的对象。 - abarnert
@abarnert 函数不是数据。 "函数字面量" 是一个误称 - 因为函数可以包含任意代码,包括赋值和各种 "不可序列化" 的东西。 - deprecated

42
您可以使用扩展属性来复制一个对象而不包含引用。但请注意(参见注释),此“复制”仅在最低级别的对象/数组层次上进行。嵌套属性仍然是引用!

完全克隆:

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

// => mutate without references:

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

克隆带有第二级引用:

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

// => nested object is still a references:

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

实际上,JavaScript 并不原生支持深克隆。可以使用一个实用函数。例如 Ramda:

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


1
这个不起作用... 当x是一个数组时,它可能会起作用,例如x=[ 'ab','cd',...]。 - Kamil Kiełczewski
4
这个可行,但请记住这是一份浅拷贝,因此对其他对象的深层引用仍然保留引用! - Bugs Bunny
部分克隆也可以通过以下方式实现:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first} - Christian Vincenzo Traina

39
const objClone = { ...obj };

请注意,嵌套对象仍然被复制为引用。


2
谢谢你提醒我嵌套对象仍然被复制为引用!我在调试代码时几乎疯了,因为我修改了“克隆”的嵌套属性,但原始对象也被修改了。 - Benny Code
这是ES2016,不是2018,而且这个答案是在两年前给出的(https://dev59.com/A3RB5IYBdhLWcg3wET5J#41183504)。 - Dan Dascalescu
那么如果我也想要嵌套属性的副本呢? - Sunil Garg
2
@SunilGarg 如果你想要复制嵌套属性,你可以使用 const objDeepClone = JSON.parse(JSON.stringify(obj)); - Pavan Garre

30

以下内容翻译自Brian Huisman的文章:如何在Javascript中复制数组和对象

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

4
这个很接近,但不适用于任何对象。试着用这个方法克隆一个日期对象。并非所有属性都是可枚举的,因此它们不会全部显示在for/in循环中。 - A. Levy
像这样添加到对象原型破坏了我的jQuery。即使我将其重命名为clone2。 - iPadDeveloper2011
3
上面的代码存在一个错误,它创建了一个名为“i”的全局变量('for i in this'),而不是使用 'for var i in this'。我有足够的声望来编辑和修复它,所以我已经这样做了。 - mikemaccana
2
@Calvin:这应该被创建为一个非枚举属性,否则“clone”将出现在“for”循环中。 - mikemaccana
3
为什么 var copiedObj = Object.create(obj); 不是一个很好的方法呢? - Dan P.
@Dany 如果克隆对象有一个名为 info 的属性,而且 info 对象有一些属性,包括一个 weight 属性。那么赋值 myclone.info.weight 将会修改原型,因为它从原型中读取 info,然后将其分配给该属性的 weight 属性。 - doug65536

29

如果您使用AngularJS,该库还提供了直接克隆或扩展对象的方法。

var destination = angular.copy(source);
或者
angular.copy(source, destination);

更多关于angular.copy的内容请查看文档...


3
这是一份深拷贝的副本,供您参考。 - zamnuts

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

11
这个回答基本正确,但不完全正确。如果你尝试克隆一个Date对象,你将得不到相同的日期,因为调用Date构造函数会使用当前日期/时间初始化新的Date。该值不可枚举,并且不会被for/in循环复制。 - A. Levy
不是完美的,但对于那些基本情况来说还不错。例如,允许简单克隆可以是基本对象、数组或字符串的参数。 - james_womack
因为使用了new正确调用了构造函数,所以我点了赞。而被接受的答案则没有这么做。 - GetFree
适用于Node的一切!其他仍有参考链接。 - user956584
递归的思想很好。但是如果值是数组,它能工作吗? - Q10Viking

26

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


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

25

性能

今天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个测试

  • shallow-small: 有10个非嵌套字段的对象 - 你可以在这里运行它
  • shallow-big: 有1000个非嵌套字段的对象 - 你可以在这里运行它
  • deep-small: 有10层嵌套字段的对象 - 你可以在这里运行它
  • deep-big: 有1000层嵌套字段的对象 - 你可以在这里运行它

测试用到的对象在下面的片段中显示

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对浅层大对象的示例结果:

在此输入图片描述


24

使用 Lodash:

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

6
天啊,重复克隆将是疯狂的举动。这是唯一明智的选择。 - Dan Ross
7
我更喜欢使用_.cloneDeep(x),因为它本质上与上面的代码相同,但更易于阅读。 - garbanzio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接