不可变性 (Immutability)
1. 什么是不可变性?
不可变性是指一个数据对象一旦被创建,其状态就不能再被修改。如果需要修改数据,必须创建一个包含了变更的新副本,而保证原始对象保持不变。
在 JavaScript 中,能够真正不可变的数据类型主要是原始类型(Primitive Types):string, number, boolean, null, undefined, symbol。对象(Object)和数组(Array)默认是可变的。
2. 为什么需要不可变性?
- 可预测性:由于数据不会在不知情的情况下被修改,更容易追踪状态的变化。
- 避免副作用:函数不会修改传入的参数,从而保证纯函数的特性。
- 状态管理:在 Redux 等状态库中,通过比较新旧对象的引用(
oldState === newState)可以快速判断数据是否变化,从而决定是否重新渲染视图(Time Travel Debugging 也依赖于此)。
3. 常见的实现方式
使用 ES6 扩展运算符 (...)
这是最常用的轻量级不可变更新方式。
javascript
// 数组
const numbers = [1, 2, 3];
// ❌ 错误:修改原数组
// numbers.push(4);
// ✅ 正确:创建新数组
const newNumbers = [...numbers, 4];
// 对象
const user = { name: 'Alice', age: 20 };
// ✅ 正确:更新属性
const updatedUser = { ...user, age: 21 };Object.freeze()
Object.freeze() 可以冻结对象,使其属性无法被修改。但它只是浅冻结。
javascript
const obj = Object.freeze({
value: 1,
nested: { a: 1 }
});
obj.value = 2; // 无效(严格模式下报错)
obj.nested.a = 2; // 有效!因为嵌套对象没有被冻结专业库 (Immer / Immutable.js)
对于复杂的嵌套对象,手动使用 spread 运算符会非常繁琐。
Immer.js: 使用 Proxy 允许你写出"看起来像可变"的代码,但实际上产生不可变更新。
javascript
import produce from "immer";
const baseState = [
{todo: "Learn TypeScript", done: true},
{todo: "Try Immer", done: false}
];
const nextState = produce(baseState, draftState => {
// 看起来像是直接修改,但 immer 会处理成不可变更新
draftState.push({todo: "Tweet about it"});
draftState[1].done = true;
});