Skip to content

不可变性 (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;
});

MIT Licensed | Keep Learning.