【问题标题】:Deep copy in ES6 using the spread syntax使用扩展语法在 ES6 中进行深度复制
【发布时间】:2016-11-19 20:08:30
【问题描述】:

我正在尝试为我的 Redux 项目创建一个深拷贝映射方法,该方法将使用对象而不是数组。我读到在 Redux 中,每个状态都不应该改变之前的状态。

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

有效:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

但是它不会深度复制内部项目,所以我需要将其调整为:

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

这不太优雅,因为它需要知道传递了哪些对象。 ES6 中有没有办法使用扩展语法来深度复制对象?

【问题讨论】:

  • 这是一个 XY 问题。您不必在 redux 中的深层属性上做太多工作。相反,您应该只创建另一个在状态形状的子切片上工作的减速器,然后使用 combineReducers 将两个(或更多)组合在一起。如果您使用惯用的 redux 技术,您的深度克隆对象问题就会消失。
  • “在 ES6 中有没有办法使用扩展语法来深度复制一个对象?”。对于一般情况,不可能。在任何级别的顶部调用展开语法会覆盖应合并的具有深度的属性。

标签: javascript ecmascript-6 redux spread-syntax


【解决方案1】:

ES6 没有内置这样的功能。我认为您有几个选择,具体取决于您想做什么。

如果你真的想深拷贝:

  1. 使用库。例如,lodash 有一个cloneDeep 方法。
  2. 实现您自己的克隆功能。

针对您的特定问题的替代解决方案(无深拷贝)

但是,我认为,如果您愿意更改一些内容,则可以为自己节省一些工作。我假设你控制所有调用站点到你的函数。

  1. 指定传递给mapCopy 的所有回调必须返回新对象,而不是改变现有对象。例如:

    mapCopy(state, e => {
      if (e.id === action.id) {
        return Object.assign({}, e, {
          title: 'new item'
        });
      } else {  
        return e;
      }
    });
    

    这利用Object.assign 创建一个新对象,在该新对象上设置e 的属性,然后在该新对象上设置一个新标题。这意味着您永远不会改变现有对象,而只会在必要时创建新对象。

  2. mapCopy 现在可以很简单了:

    export const mapCopy = (object, callback) => {
      return Object.keys(object).reduce(function (output, key) {
        output[key] = callback.call(this, object[key]);
        return output;
      }, {});
    }
    

基本上,mapCopy 相信它的调用者会做正确的事。这就是为什么我说这假设您控制所有呼叫站点。

【讨论】:

  • Object.assign 不会深拷贝对象。请参阅developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign() 复制属性值。 “如果源值是对对象的引用,它只会复制该引用值。”
  • 对。这是一个涉及深度复制的替代解决方案。我会更新我的答案以更明确地说明这一点。
【解决方案2】:

来自 MDN

注意:扩展语法在复制数组时有效地深入了一层。因此,它可能不适合复制多维数组,如下例所示(与 Object.assign() 和展开语法相同)。

我个人建议使用Lodash's cloneDeep 函数进行多级对象/数组克隆。

这是一个工作示例:

const arr1 = [{ 'a': 1 }];

const arr2 = [...arr1];

const arr3 = _.clone(arr1);

const arr4 = arr1.slice();

const arr5 = _.cloneDeep(arr1);

const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!


// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false

// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

【讨论】:

  • arr6 不适合我。在浏览器中(支持 ES6 的 chrome 59.0 我得到 Uncaught SyntaxError: Unexpected token ... 和在支持 ES7 的节点 8.9.3 中我得到 TypeError: undefined is not a functionat repl:1: 22
  • @AchiEven-dar 不知道为什么你会出错。您可以通过按下蓝色按钮Run code snippet 直接在stackoverflow 中运行此代码,它应该可以正常运行。
  • arr6 也不适合我。在浏览器中 - chrome 65
  • arr6 可能会在某些实现中编译,但它永远不会像作者期望的那样。
【解决方案3】:

使用 JSON 进行深拷贝

var newObject = JSON.parse(JSON.stringify(oldObject))

var oldObject = {
  name: 'A',
  address: {
    street: 'Station Road',
    city: 'Pune'
  }
}
var newObject = JSON.parse(JSON.stringify(oldObject));

newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);

【讨论】:

  • 这仅适用于您不需要克隆函数的情况。 JSON 将忽略所有函数,因此您不会将它们包含在克隆中。
  • 除了函数,使用这种方法还会遇到 undefined 和 null 的问题
  • 任何用户定义的类也会有问题,因为原型链没有序列化。
  • 您使用 JSON 序列化的解决方案存在一些问题。通过这样做,您将丢失任何在 JSON 中没有等效类型的 Javascript 属性,例如 Function 或 Infinity。 JSON.stringify 将忽略分配给 undefined 的任何属性,从而导致它们在克隆对象上丢失。此外,某些对象会转换为字符串,例如 Date、Set、Map 和许多其他对象。
  • 我做了一个可怕的噩梦,试图创建一个对象数组的真实副本——对象本质上是数据值,没有函数。如果您只需要担心这些,那么这种方法效果很好。
【解决方案4】:
function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}

【讨论】:

    【解决方案5】:

    我经常用这个:

    function deepCopy(obj) {
        if(typeof obj !== 'object' || obj === null) {
            return obj;
        }
    
        if(obj instanceof Date) {
            return new Date(obj.getTime());
        }
    
        if(obj instanceof Array) {
            return obj.reduce((arr, item, i) => {
                arr[i] = deepCopy(item);
                return arr;
            }, []);
        }
    
        if(obj instanceof Object) {
            return Object.keys(obj).reduce((newObj, key) => {
                newObj[key] = deepCopy(obj[key]);
                return newObj;
            }, {})
        }
    }
    

    【讨论】:

      【解决方案6】:
      // use: clone( <thing to copy> ) returns <new copy>
      // untested use at own risk
      function clone(o, m){
        // return non object values
        if('object' !==typeof o) return o
        // m: a map of old refs to new object refs to stop recursion
        if('object' !==typeof m || null ===m) m =new WeakMap()
        var n =m.get(o)
        if('undefined' !==typeof n) return n
        // shallow/leaf clone object
        var c =Object.getPrototypeOf(o).constructor
        // TODO: specialize copies for expected built in types i.e. Date etc
        switch(c) {
          // shouldn't be copied, keep reference
          case Boolean:
          case Error:
          case Function:
          case Number:
          case Promise:
          case String:
          case Symbol:
          case WeakMap:
          case WeakSet:
            n =o
            break;
          // array like/collection objects
          case Array:
            m.set(o, n =o.slice(0))
            // recursive copy for child objects
            n.forEach(function(v,i){
              if('object' ===typeof v) n[i] =clone(v, m)
            });
            break;
          case ArrayBuffer:
            m.set(o, n =o.slice(0))
            break;
          case DataView:
            m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
            break;
          case Map:
          case Set:
            m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
            break;
          case Int8Array:
          case Uint8Array:
          case Uint8ClampedArray:
          case Int16Array:
          case Uint16Array:
          case Int32Array:
          case Uint32Array:
          case Float32Array:
          case Float64Array:
            m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
            break;
          // use built in copy constructor
          case Date:
          case RegExp:
            m.set(o, n =new (c)(o))
            break;
          // fallback generic object copy
          default:
            m.set(o, n =Object.assign(new (c)(), o))
            // recursive copy for child objects
            for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
        }
        return n
      }
      

      【讨论】:

      • 代码中的注释供那些寻求解释的人使用。
      【解决方案7】:
      const cloneData = (dataArray) => {
          newData= []
          dataArray.forEach((value) => {
              newData.push({...value})
          })
          return newData
      }
      
      • a = [{name:"siva"}, {name:"siva1"}] ;
      • b = myCopy(a)
      • b === a // 错误`

      【讨论】:

        【解决方案8】:

        我自己昨天得到了这些答案,试图找到一种方法来深度复制复杂结构,其中可能包括递归链接。由于我对之前提出的任何建议都不满意,所以我自己实现了这个轮子。而且效果很好。希望它可以帮助某人。

        示例用法:

        OriginalStruct.deep_copy = deep_copy; // attach the function as a method
        
        TheClone = OriginalStruct.deep_copy();
        

        请查看https://github.com/latitov/JS_DeepCopy 了解如何使用它的实时示例,还有 deep_print()。

        如果你需要它,这里是 deep_copy() 函数的来源:

        function deep_copy() {
            'use strict';   // required for undef test of 'this' below
        
            // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.
        
            var id_cnt = 1;
            var all_old_objects = {};
            var all_new_objects = {};
            var root_obj = this;
        
            if (root_obj === undefined) {
                console.log(`deep_copy() error: wrong call context`);
                return;
            }
        
            var new_obj = copy_obj(root_obj);
        
            for (var id in all_old_objects) {
                delete all_old_objects[id].__temp_id;
            }
        
            return new_obj;
            //
        
            function copy_obj(o) {
                var new_obj = {};
                if (o.__temp_id === undefined) {
                    o.__temp_id = id_cnt;
                    all_old_objects[id_cnt] = o;
                    all_new_objects[id_cnt] = new_obj;
                    id_cnt ++;
        
                    for (var prop in o) {
                        if (o[prop] instanceof Array) {
                            new_obj[prop] = copy_array(o[prop]);
                        }
                        else if (o[prop] instanceof Object) {
                            new_obj[prop] = copy_obj(o[prop]);
                        }
                        else if (prop === '__temp_id') {
                            continue;
                        }
                        else {
                            new_obj[prop] = o[prop];
                        }
                    }
                }
                else {
                    new_obj = all_new_objects[o.__temp_id];
                }
                return new_obj;
            }
            function copy_array(a) {
                var new_array = [];
                if (a.__temp_id === undefined) {
                    a.__temp_id = id_cnt;
                    all_old_objects[id_cnt] = a;
                    all_new_objects[id_cnt] = new_array;
                    id_cnt ++;
        
                    a.forEach((v,i) => {
                        if (v instanceof Array) {
                            new_array[i] = copy_array(v);
                        }
                        else if (v instanceof Object) {
                            new_array[i] = copy_object(v);
                        }
                        else {
                            new_array[i] = v;
                        }
                    });
                }
                else {
                    new_array = all_new_objects[a.__temp_id];
                }
                return new_array;
            }
        }
        

        干杯@!

        【讨论】:

          【解决方案9】:
          const a = {
            foods: {
              dinner: 'Pasta'
            }
          }
          let b = JSON.parse(JSON.stringify(a))
          b.foods.dinner = 'Soup'
          console.log(b.foods.dinner) // Soup
          console.log(a.foods.dinner) // Pasta
          

          使用JSON.stringifyJSON.parse 是最好的方法。因为通过使用扩展运算符,当 json 对象中包含另一个对象时,我们不会得到有效的答案。我们需要手动指定。

          【讨论】:

            【解决方案10】:

            这是处理所有原始、数组、对象、函数数据类型的 deepClone 函数

            function deepClone(obj){
            	if(Array.isArray(obj)){
            		var arr = [];
            		for (var i = 0; i < obj.length; i++) {
            			arr[i] = deepClone(obj[i]);
            		}
            		return arr;
            	}
            
            	if(typeof(obj) == "object"){
            		var cloned = {};
            		for(let key in obj){
            			cloned[key] = deepClone(obj[key])
            		}
            		return cloned;	
            	}
            	return obj;
            }
            
            console.log( deepClone(1) )
            
            console.log( deepClone('abc') )
            
            console.log( deepClone([1,2]) )
            
            console.log( deepClone({a: 'abc', b: 'def'}) )
            
            console.log( deepClone({
              a: 'a',
              num: 123,
              func: function(){'hello'},
              arr: [[1,2,3,[4,5]], 'def'],
              obj: {
                one: {
                  two: {
                    three: 3
                  }
                }
              }
            }) ) 

            【讨论】:

              【解决方案11】:

              这是我的深拷贝算法。

              const DeepClone = (obj) => {
                   if(obj===null||typeof(obj)!=='object')return null;
                  let newObj = { ...obj };
              
                  for (let prop in obj) {
                    if (
                      typeof obj[prop] === "object" ||
                      typeof obj[prop] === "function"
                    ) {
                      newObj[prop] = DeepClone(obj[prop]);
                    }
                  }
              
                  return newObj;
                };
              

              【讨论】:

              • 您还需要检查 'obj[prop]!==null' as typeof(null) 是否也返回 'object'
              【解决方案12】:

              我建议使用扩展运算符。如果您需要更新第二个级别,则需要进行第二次传播。如果地址在 oldObject 中不存在,尝试使用类似 newObject.address.city 的方式更新 newObject 将引发错误。

              const oldObject = {
                name: 'A',
                address: {
                  street: 'Station Road',
                  city: 'Pune'
                }
              }
              
              const newObject = {
                ...oldObject,
                address: {
                  ...oldObject.address,
                  city: 'Delhi'
                }
              }
              
              console.log(newObject)
              

              【讨论】:

                猜你喜欢
                • 2018-05-01
                • 1970-01-01
                • 2017-12-24
                • 2020-02-24
                • 2017-07-27
                • 2019-01-28
                相关资源
                最近更新 更多