使用扩展语法在 ES6 中进行深度复制

IT技术 javascript ecmascript-6 redux spread-syntax
2021-01-27 00:25:41

我正在尝试为我的 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 中有没有办法使用扩展语法来深度复制对象?

6个回答

使用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 将忽略所有函数,因此您不会在克隆中使用它们。
2021-03-17 00:25:41
您还会遇到任何用户定义类的问题,因为原型链未序列化。
2021-03-19 00:25:41
您使用 JSON 序列化的解决方案存在一些问题。通过这样做,您将丢失任何在 JSON 中没有等效类型的 Javascript 属性,例如 Function 或 Infinity。任何分配给 undefined 的属性都将被 JSON.stringify 忽略,导致它们在克隆对象上丢失。此外,某些对象会转换为字符串,例如 Date、Set、Map 等。
2021-03-24 00:25:41
我做了一个可怕的噩梦,试图创建一个对象数组的真实副本——对象本质上是数据值,没有函数。如果您只需要担心这些,那么这种方法效果很好。
2021-04-04 00:25:41
除了函数之外,使用此方法还会遇到 undefined 和 null 问题
2021-04-12 00:25:41

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是相信调用者会做正确的事情。这就是为什么我说这假设您控制所有呼叫站点。

对。这是一种涉及深度复制的替代解决方案我会更新我的答案以更明确地说明这一点。
2021-03-31 00:25:41
Object.assign 不会深度复制对象。请参阅developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - Object.assign() 复制属性值。“如果源值是对对象的引用,它只会复制该引用值。”
2021-04-12 00:25:41

来自 MDN

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

就个人而言,我建议使用Lodash 的 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>

@AchiEven-dar 不知道你为什么出错。您可以通过按蓝色按钮直接在 stackoverflow 中运行此代码Run code snippet,它应该可以正确运行。
2021-03-17 00:25:41
arr6 也不适合我。在浏览器中 - chrome 65
2021-03-18 00:25:41
arr6 不适合我。在浏览器中(支持 ES6 的 chrome 59.0 我得到Uncaught SyntaxError: Unexpected token ...而在支持 ES7 的节点 8.9.3 我得到TypeError: undefined is not a functionat repl:1:22
2021-03-25 00:25:41
arr6 可能会在某些实现中编译,它只是永远不会做作者期望它做的事情。
2021-04-01 00:25:41

我经常使用这个:

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;
        }, {})
    }
}
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 对象包含另一个对象时,我们将无法得到有效的答案。我们需要手动指定。