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

如何正确克隆JavaScript对象?


当前回答

复制对象最正确的方法是使用object.create:

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

这样的符号将使相同的对象具有正确的原型和隐藏的财产。

其他回答

表演

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

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

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

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

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

根据Apple JavaScript编码指南:

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

史蒂夫

您可以简单地使用扩展属性来复制没有引用的对象。但是要小心(请参见注释),“副本”仅位于最低的对象/数组级别。嵌套的财产仍然是引用!


完整克隆:

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

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

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

在这种继承有用的地方(duh!)源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这种解决方案更优越?它是本地的,因此没有循环,没有递归。然而,较旧的浏览器将需要polyfill。