【问题标题】:Is this a good way to clone an object in ES6?这是在 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,它是 有效的 ES9developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

标签: javascript ecmascript-6 javascript-objects


【解决方案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 的功能是可怕的......
  • 如果您需要通用函数来深度克隆任何对象,请查看stackoverflow.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。为什么要使用相同的解决方案发布另一个答案?
          猜你喜欢
          • 2014-07-02
          • 2015-06-01
          • 1970-01-01
          • 2012-06-19
          • 1970-01-01
          • 1970-01-01
          • 2011-05-30
          • 2011-02-16
          相关资源
          最近更新 更多