这是在 ES6 中克隆对象的好方法吗?

Posted

技术标签:

【中文标题】这是在 ES6 中克隆对象的好方法吗?【英文标题】:Is this a good way to clone an object in ES6? 【发布时间】:2017-02-05 18:40:33 【问题描述】:

谷歌搜索“javascript clone object”会带来一些非常奇怪的结果,其中一些已经过时了,而另一些则太复杂了,这不是那么简单吗:

let clone = ...original;

这有什么问题吗?

【问题讨论】:

这不是合法的 ES6。但如果是,这不是克隆:您的克隆和原始属性现在都指向相同的东西。例如,original = a: [1,2,3] 给你一个克隆,clone.a 字面意思是original.a。通过cloneoriginal 进行修改会修改相同的东西,所以不,这很糟糕=) @AlbertoRivera 这是有点有效的 JavaScript,因为它是一个 stage 2 提案,可能会成为 JavaScript 标准的未来补充。 @Frxstrem 的问题是关于 ES6,这不是有效的 JavaScript =) 浅克隆还是深层克隆? 你是对的,它不是有效的 ES6,它是 有效的 ES9。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 【参考方案1】:

这对浅层克隆很有用。 object spread is a standard part of ECMAScript 2018。

对于深度克隆,您需要different solution。

const clone = ...original 浅克隆

const newobj = ...original, prop: newOne 不变地将另一个道具添加到原始道具并存储为新对象。

【讨论】:

但是,这不只是一个浅克隆吗?如,属性不是递归克隆的,是吗?因此, original.innerObject === clone.innerObject 和更改 original.innerObject.property 将更改 clone.innerObject.property。 是的,这是一个浅克隆。如果你想要一个深度克隆,你必须使用JSON.parse(JSON.stringify(input)) /!\ JSON.parse(JSON.stringify(input)) 弄乱了日期,未定义,... 这不是克隆的灵丹妙药!见:maxpou.fr/immutability-js-without-library 那么 hack JSON.stringify()/JSON.parse() 真的是在 ES6 中深度克隆对象的推荐方法吗?我一直看到它推荐。令人不安。 @MarkShust JSON.parse(JSON.stringify(input)) 将不起作用,因为如果有 functionsinfinity 作为值,它将简单地分配 null 代替它们的位置。只有在简单的值是literals 而不是functions 时,它才会起作用。【参考方案2】:

编辑:发布此答案时,...obj 语法在大多数浏览器中不可用。现在,您应该可以使用它了(除非您需要支持 IE 11)。

使用 Object.assign。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj =  a: 1 ;
var copy = Object.assign(, obj);
console.log(copy); //  a: 1 

但是,这不会进行深度克隆。目前还没有本地的深度克隆方法。

编辑:正如 @Mike 'Pomax' Kamermans 在 cmets 中提到的,您可以使用 JSON.parse(JSON.stringify(input)) 深度克隆简单对象(即没有原型、函数或循环引用)

【讨论】:

有一个,前提是您的对象是真正的对象字面量和纯数据,在这种情况下,JSON.parse(JSON.stringify(input)) 是一个适当的深度克隆。但是,一旦原型、函数或循环引用发挥作用,该解决方案就不再有效。 @Mike'Pomax'Kamermans 这是真的。但是,失去 getter 和 setter 的功能是可怕的...... 如果您需要通用函数来深度克隆任何对象,请查看***.com/a/13333781/560114。 现在有办法做到deep cloning natively。 @DanDascalescu 尽管它是实验性的,但它看起来很有希望。感谢您的信息!【参考方案3】:

如果您使用的方法不适用于涉及 Date 等数据类型的对象,请试试这个

导入_

import * as _ from 'lodash';

深度克隆对象

myObjCopy = _.cloneDeep(myObj);

【讨论】:

只需import _ from 'lodash'; 就足够了。但是 +1 表示“不要重新发明***”的答案。 lodash 臃肿。真的不需要为了一个简单的深拷贝而引入 lodash。这里有很多其他解决方案。对于希望构建精益应用的 Web 开发人员来说,这是一个非常糟糕的答案。 Webpack tree-shaking 是 Jason 的解决方案。您也可以只导入该函数:npmjs.com/package/lodash.clonedeep。 +1 使用已知良好的解决方案而不是重新发明***【参考方案4】:

如果你不想使用 json.parse(json.stringify(object)) 你可以递归地创建键值副本:

function copy(item)
  let result = null;
  if(!item) return result;
  if(Array.isArray(item))
    result = [];
    item.forEach(element=>
      result.push(copy(element));
    );
  
  else if(item instanceof Object && !(item instanceof Function)) 
    result = ;
    for(let key in item)
      if(key)
        result[key] = copy(item[key]);
      
    
  
  return result || item;

但最好的方法是创建一个可以返回它自己的克隆的类

class MyClass
    data = null;
    constructor(values) this.data = values 
    toString() console.log("MyClass: "+this.data.toString(;) 
    remove(id) this.data = data.filter(d=>d.id!==id) 
    clone() return new MyClass(this.data) 

【讨论】:

【参考方案5】:

你也可以这样做,

let copiedData = JSON.parse(JSON.stringify(data));

【讨论】:

这会起作用,但是对象的数据类型变成了字符串 :( 例如,当使用 stringify 时,日期对象变成了一个具有转换值的字符串【参考方案6】:

根据@marcel 的回答,我发现克隆对象上仍然缺少一些功能。例如

function MyObject() 
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", 
    get: function()  return methodAValue; ,
    set: function(value) 
      methodAValue = value || ;
    ,
    enumerable: true
  );

  Object.defineProperty(this, "methodB", 
    get: function()  return methodAValue; ,
    set: function(value) 
      methodAValue = value || ;
    
  );

我可以在 MyObject 上克隆 methodA 但排除 methodB 的位置。发生这种情况是因为它丢失了

enumerable: true

这意味着它没有出现在

for(let key in item)

我改用了

Object.getOwnPropertyNames(item).forEach((key) => 
    ....
  );

这将包括不可枚举的键。

我还发现原型(proto)没有被克隆。为此我最终使用了

if (obj.__proto__) 
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);

PS:令人沮丧的是我找不到内置函数来执行此操作。

【讨论】:

【参考方案7】:
We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = 
     name: "John",
     age: 30
    ;

    let clone = ; // the new empty object

    // let's copy all user properties into it
    for (let key in user) 
      clone[key] = user[key];
    

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user =  name: "John" ;
    let permissions1 =  canView: true ;
    let permissions2 =  canEdit: true ;

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = 
      name: "John",
      age: 30
    ;

    let clone = Object.assign(, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

但 Object.assign() 不会创建深层克隆

let user = 
  name: "John",
  sizes: 
    height: 182,
    width: 50
  
;

let clone = Object.assign(, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

为了解决这个问题,我们应该使用克隆循环来检查 user[key] 的每个值,如果它是一个对象,那么也复制它的结构。这就是所谓的“深度克隆”。

有一种标准的深度克隆算法可以处理上述情况和更复杂的情况,称为结构化cloning algorithm。 为了不重新发明***,我们可以使用 JavaScript 库 lodash 中的工作实现,该方法称为 _.cloneDeep(obj)。

【讨论】:

【参考方案8】:

我找到了一个似乎也复制函数的解决方案,如果此示例有错误,请纠正我。

注意我没有用更复杂的对象案例测试这个方法,例如,它会包含带有 this 的方法以供参考

以早餐的价格为例,我在全球范围内都有这个价格,但我想针对酒店房间单独调整它

// make an object for a booking option
var opt_resa =  breakfast_val: 900 

// i define a function for opt_resa : 
opt_resa.func = function() alert('i am a function'); 

// copy object in modif.opt_resa :
var modif =  opt_resa :  

for ( var v in opt_resa )

    modif.opt_resa[v] = $.o.opt_resa[v];


// test
modif.opt_resa.breakfast_val = 1500;

// old value
console.log( opt_resa.breakfast_val );
// output : 900

// modified value
console.log( modif.opt_resa.breakfast_val );
// output : 1500

// function copied
modif.opt_resa.func(); 
// this function works

【讨论】:

【参考方案9】:

上述所有方法都不能处理嵌套到 n 层的对象的深度克隆。我没有检查它的性能,但它简短而简单。

下面的第一个示例显示了使用Object.assign 进行的对象克隆,它只克隆到第一级。

var person = 
    name:'saksham',
    age:22,
    skills: 
        lang:'javascript',
        experience:5
    


newPerson = Object.assign(,person);
newPerson.skills.lang = 'angular';
console.log(newPerson.skills.lang); //logs Angular

使用下面的方法深度克隆对象

var person = 
    name:'saksham',
    age:22,
    skills: 
        lang:'javascript',
        experience:5
    


anotherNewPerson = JSON.parse(JSON.stringify(person));
anotherNewPerson.skills.lang = 'angular';
console.log(person.skills.lang); //logs javascript

【讨论】:

JSON.parse/stringify 被认为是 years 的一种糟糕的深度克隆方法。请检查以前的答案以及相关问题。此外,这对 ES6 来说并不新鲜。 @DanDascalescu 我知道这一点,我认为将它用于简单对象应该不是问题。其他人也在同一篇文章的回答中提到了这一点,甚至作为 cmets。我认为它不值得一票否决。 完全正确-“其他人在他们的答案中也提到了”JSON.parse/stringify。为什么要使用相同的解决方案发布另一个答案?

以上是关于这是在 ES6 中克隆对象的好方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

这是在列表上执行 CRUD 操作的好方法吗

这是在Laravel中一次发送多封邮件的好方法吗?

使用多态性是在一个容器下存储不同数据类型的好方法吗?

ByteBuffers 是在 Android 中存储 > 1MB 数据的好方法吗?

这是暂时改变当前线程文化的好方法吗?

这是在 Vue 中下载文件的正确方法吗?