使用 Object.assign 对对象所做的更改会改变源对象

Changes to object made with Object.assign mutates source object

我的 react-redux 应用程序中有以下 reducer 代码:

    case 'TOGGLE_CLIENT_SELECTION':
        const id = action.payload.id;
        let newState = Object.assign({}, state);
        newState.list.forEach((client) => {
            if (client.id == id) client.selected = !client.selected;
        });
        console.log(state.list[0].selected + ' - ' + newState.list[0].selected)
        return newState;

如果我做对了 - Object.assign 创建了全新的对象,但是 console.log 显示 "true - true" of "false - false"。有什么想法为什么会这样吗?我该如何避免这种行为?

没错,但这不是深拷贝。

新对象包含对旧列表的引用。

这里有一个绕过它的技巧(有些人可能会说更多 "proper" 方法):

JSON.stringify原来的。然后 JSON.parse 那个字符串。新对象将是一个 "deep" 副本(不确定这在技术上是否真的 "deep copying")。它工作正常,除非你的子类型比标准的普通旧 JSON 可接受的东西更复杂。

Object.assign 创建浅克隆

Object.assign 会将属性复制到另一个对象。其中一些属性被复制 "by value",其他属性被复制 "by reference"。字符串、数字和布尔值是 "by value",而对象(包括日期)是 "by reference",这意味着它们指向同一个对象。

因此,例如,给定:

var otherState = { count: 10 };

var state = { text: "foo", otherState: otherState };

var newState = Object.assign({}, state);

newState.text = "bar";
newState.otherState.count = 9;

newState.text 将分配一个全新的字符串,状态不会受到影响。但是 statenewState 都引用 otherState 的同一个实例,所以当你将 count 更新为 9 时,state.otherState.countnewState.otherState.count 都是受影响:

state = {
    text = "foo"
    otherState ──────┐
  }                  │
                     │
                     │
                     ├────> { count = 9 }
                     │
newState = {         │
    text = "bar"     │
    otherState ──────┘
  }

当使用Object.assign时,使用三的规则:如果你到达第三个属性,你现在工作在"shared"状态:

newState.otherState.count = 0;
//  ^        ^        ^
//  ╵        ╵        ╵
//  1        2        3

JSON.parse 救援 (?)

一样,一种快速简便的解决方法是使用 JSON.stringify:

let newState = JSON.parse(JSON.stringify(state));

但这并非万无一失。它适用于一些简单的场景,但有许多场景可能会失败。例如,循环引用 break JSON.stringify:

let x = { id: "x" },
y = { id: "y" };
x.y = y;
y.x = x;

// fails with: "Uncaught TypeError: Converting circular structure to JSON"
let cloneX = JSON.parse(JSON.stringify(x));

自定义克隆代码来拯救 (?)

如果您知道要克隆的结构,那么您可以使用自己的逻辑来处理复制,而不会陷入无限循环:

function cloneX( x ){
    const c_x = { id: x.id };
    const c_y = { id: x.y.id };
    c_x.y = c_y;
    c_y.x = c_x;
    return c_x;
}

let x = { id: "x" },
    y = { id: "y" };
x.y = y;
y.x = x;

let x2 = cloneX(x);
x2.y.id = "y2";

console.log( `x2.y.id was updated: ${x2.y.id}` );
console.log( `x.y.id is unchanged: ${x.y.id}` );

还可以想象,通过对 WeakMap 的一些创造性使用,您可以想出一些通过跟踪递归和允许深度复制来处理未知数据结构的逻辑。

NPM 来拯救 (?)

不过 use a library 可能更容易。

试试这个:let newState = state.map(item => Object.assign({}, ...item)) 这将创建一个不引用旧对象的新对象 state

根据 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assignObject.assign 修改 target 对象而不是创建新对象。

您可以使用以下方法进行深拷贝,然后使用 Object.asign:

const x = { id: "x" },
y = { id: "y" };
x.y = y;
y.x = x;
const cloneX = Object.fromEntries(Object.entries(x));

const cloneChangeX = Object.assign(cloneX, {z: {id: "z"}});

console.log('const x = ', x, '\nconst cloneX = ', cloneX, '\nconst cloneChangeX = ', cloneChangeX);

最后,你可以这样做:

const cloneChangeX = Object.assign(
  Object.fromEntries(Object.entries(x)),
  { changekey: changevalue }
);