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

如何正确克隆JavaScript对象?


当前回答

好的,我知道它有很多答案,但没有人指出,EcmaScript5有赋值方法,在FF和Chrome上工作,它复制可枚举的,并且拥有财产和符号。

对象指定(Object Assign)

其他回答

我认为,在没有库的情况下,缓存的重复性是最好的。

被低估的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 of循环、=>运算符和WeakMaps来创建更重要的代码。当前的浏览器支持此语法(ES6)

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

本文来自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;
};

表演

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

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

浅层副本的结果

解决方案{…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)和1000级深对象的Chrome上的U崩溃解决方案

细节

对于选择的解决方案:A.BC(我的)DEFGH我JKLMNOPQRSTU我进行了4次测试

浅小:具有10个非嵌套字段的对象-可以在此处运行浅大:具有1000个非嵌套字段的对象-可以在此处运行deep-small:具有10级嵌套字段的对象-可以在此处运行deep-big:具有1000级嵌套字段的对象-可以在此处运行

测试中使用的对象显示在下面的代码段中

let obj_ShallowSmall={字段0:假,字段1:真,字段2:1,字段3:0,字段4:空,字段5:[],字段6:{},field7:“text7”,字段8:“text8”,}让obj_DepSmall={级别0:{级别1:{级别2:{级别3:{级别4:{级别5:{级别6:{第7级:{级别8:{第9级:[[[[[[[[]],}}}}}}}}},};让obj_ShallowBig=数组(1000).fill(0).reduce((a,c,i)=>(a['field'+i]=getField(i),a),{});让obj_DepBig=genDeepObject(1000);// ------------------//显示对象// ------------------console.log('obj_ShallowSmall:',JSON.stringify(obj_SharlowSmall));console.log('obj_DepSmall:',JSON.stringify(obj_DeepSmall));console.log('obj_ShallowBig:',JSON.stringify(obj_SharlowBig));console.log('obj_DepBig:',JSON.stringify(obj_DeepBig));// ------------------//助手// ------------------函数getField(k){设i=k%10;如果(i==0)返回false;如果(i==1)返回真;如果(i==2)返回k;如果(i==3)返回0;如果(i==4)返回null;如果(i==5)返回[];如果(i==6)返回{};如果(i>=7)返回“text”+k;}函数genDeepObject(N){//生成:{level0:{level1:{…levelN:{end:[[[…N次…['abc']…]]}}}}…}}让obj={};设o=obj;设arr=[];设a=arr;for(设i=0;i<N;i++){o['level'+i]={};o=o['level'+i];设aa=[];a.推(aa);a=aa;}a[0]=“公元前”;o['end']=arr;返回obj;}

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

函数A(obj){返回{…obj}}函数B(obj){return Object.assign({},obj);}函数C(obj){return Object.keys(obj).reduce((a,c)=>(a[c]=obj[c],a),{})}函数D(obj){让copyOfObject={};Object.defineProperties(copyOfObject,Object.getOwnPropertyDescriptors(obj));返回copyOfObject;}函数E(obj){return jQuery.exextend({},obj)//浅}函数F(obj){return _.clone(obj);}函数G(obj){return _.clone(obj,true);}函数H(obj){返回扩展({},obj);}函数I(obj){如果(null==obj||“对象”!=obj类型)返回obj;var copy=obj.constructor();for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=obj[attr];}返回副本;}函数J(obj){return _.cloneDeep(obj,true);}函数K(obj){返回JSON.parse(JSON.stringify(obj));}函数L(obj){return jQuery.exextend(true,{},obj)//deep}函数M(obj){如果(obj==null | |类型(obj)!='对象')返回obj;var temp=新建obj.constructor();for(obj中的var键)temp[key]=M(obj[key]);返回温度;}函数N(obj){设EClone=函数(obj){var newObj=(数组的obj实例)?[] : {};for(对象中的变量i){如果(i=='EClone')继续;如果(obj[i]&&类型obj[i]==“对象”){newObj[i]=EClone(obj[i]);}否则newObj[i]=obj[i]}返回newObj;};返回EClone(obj);};函数O(obj){如果(obj==null||typeof obj!=“object”)返回obj;如果(obj.constructor!=对象&&obj.constructionr!=数组)返回obj;如果(obj.constructor==日期| | obj.constructionr==RegExp | | obc.constructor==函数||obj.structor==字符串||obj.structor==数字||obc.structor==布尔值)返回新的obj.constructor(obj);let to=new obj.constructor();for(obj中的var名称){to[name]=typeof to[name]==“undefined”?O(obj[名称],null):到[名称];}返回;}函数P(obj){函数克隆(目标,源){for(输入源代码){//使用getOwnPropertyDescriptor而不是source[key]来防止触发setter/getter。let descriptor=Object.getOwnPropertyDescriptor(源,键);if(字符串的descriptor.value实例){target[key]=新字符串(descriptor.value);}else if(数组的descriptor.value实例){target[key]=克隆([],descriptor.value);}else if(descriptor.value对象实例){let prototype=Reflect.getPrototypeOf(descriptor.value);let cloneObject=clone({},descriptor.value);Reflect.setPrototypeOf(克隆对象,原型);target[key]=克隆对象;}其他{Object.defineProperty(目标、键、描述符);}}let prototype=Reflect.getPrototypeOf(源代码);Reflect.setPrototypeOf(目标,原型);回归目标;}返回克隆({},obj);}函数Q(obj){var复制;//处理3个简单类型,以及null或undefined如果(null==obj||“对象”!=obj类型)返回obj;//处理日期if(日期的obj实例){copy=新日期();copy.setTime(obj.getTime());返回副本;}//句柄数组if(数组的obj实例){copy=[];对于(变量i=0,len=obj.length;i<len;i++){copy[i]=Q(obj[i]);}返回副本;}//句柄对象if(对象的obj实例){copy={};for(obj中的var属性){如果(obj.hasOwnProperty(attr))copy[attr]=Q(obj.[attr]);}返回副本;}throw new Error(“无法复制obj!不支持其类型。”);}函数R(obj){const gdcc=“__getDeepCircularCopy__”;if(obj!==对象(obj)){返回obj;//原始值}var set=obj中的gdcc,缓存=obj[gdcc],后果if(设置缓存类型==“函数”){return cache();}//其他obj[gdcc]=函数(){return result;};//覆盖if(数组的obj实例){结果=[];对于(var i=0;i<obj.length;i++){结果[i]=R(obj[i]);}}其他{结果={};for(obj中的var属性)if(prop!=gdcc)result[prop]=R(obj[prop]);else if(设置)result[prop]=R(缓存);}if(设置){obj[gdcc]=缓存;//重置}其他{删除对象[gdcc];//再次取消设置}返回结果;}函数S(obj){const cache=new WeakMap();//新旧参考地图函数副本(对象){if(对象类型!=='对象'||对象==空||HTMLElement的对象实例)返回对象;//原始值或HTMLElementif(日期的对象实例)return new Date().setTime(object.getTime());if(RegExp的对象实例)返回新的RegExp(object.source,object.flags);if(cache.has(对象))返回cache.get(对象);const result=数组的对象实例?[] : {};cache.set(对象,结果);//在递归开始之前存储对对象的引用if(数组的对象实例){for(对象的常量){result.push(复制(o));}返回结果;}const keys=对象.keys(对象);for(键的常量键)result[key]=复制(对象[key]);返回结果;}返回副本(obj);}

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

好的,这可能是浅层复制的最佳选择。If遵循了许多使用赋值的示例,但它也保留了继承和原型。它也很简单,适用于大多数类数组和对象,但有构造函数要求或只读财产的对象除外。但这意味着它对于TypedArrays、RegExp、Date、Maps、Set和Object版本的原语(Boolean、String等)失败得很惨。

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

其中a可以是任何Object或类构造的实例,但对于使用专门的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/setter、构造函数必需的参数或只读财产,以及对父亲的冒犯,因此失败。

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