如何理解Javascript的不可变性_怎样在Javascript中应用不可变数据?

JavaScript中const仅保证绑定不可变,不保证值不可变;基本类型因值传递等效不可变,引用类型需Object.freeze()或结构复制实现浅层不可变,深层不可变推荐immer等工具。

JavaScript 本身没有原生的“不可变类型”概念,const 只是阻止变量重新赋值,不保证值不可变;对象和数组默认都是可变的。理解不可变性,关键在于区分「引用不可变」和「值不可变」。

为什么 const 不等于不可变?

const 声明的只是绑定不可变:变量不能指向新地址,但若它指向的是对象,对象内部属性仍可修改。

const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ 合法:对象内容被修改
user = { name: 'Charlie' }; // ❌ 报错:Cannot assign to const variable
  • const 对基本类型(stringnumberboolean等)效果等同于不可变,因为它们是值传递
  • const 对引用类型(ObjectArrayDate等)只冻结“指针”,不冻结“内存里的内容”
  • 真正实现值不可变,需用 Object.freeze() 或结构复制(如展开运算符、structuredClone()

如何安全地更新嵌套对象而不改变原对象?

直接修改嵌套属性(如 obj.user.profile.age = 30)会污染原始数据,尤其在 React、Redux 或函数式编程中易引发意外副作用。

  • 浅层对象用展开运算符:{ ...obj, field: newValue }
  • 深层嵌套推荐用函数式工具(如 immerproduce),或手动分层展开
  • 避免滥用 JSON.parse(JSON.stringify(obj)):丢失函数、undefinedDateRegExp
const original = { user: { profile: { name: 'Alice', age: 25 } } };
// ✅ 安全更新 age
const updated = {
  ...original,
  user: {
    ...original.user,
    profile: {
      ...original.user.profile,
      age: 26
    }
  }
};

Object.freeze() 能否真正实现不可变?

它只能做浅冻结:顶层属性不可增删改,但嵌套对象仍可变。

立即学习“Java免费学习笔记(深入)”;

const obj = { a: 1, nested: { b: 2 } };
Object.freeze(obj);
obj.a = 3; // ❌ 无效
obj.nested.b = 4; // ✅ 仍然生效!
  • 要深冻结,需递归调用 Object.freeze()(注意循环引用会爆栈)
  • 生产环境一般不用深冻结——性能差、无法撤销、与大多数库(如 React DevTools)不兼容
  • 更实用的做法是约定 + 工具:用 immer 写“看似可变”的代码,底层生成新对象

现代 JS 中推荐的不可变实践路径

不追求绝对冻结,而是在关键数据流节点主动创建新值。重点不是“防住所有修改”,而是“让修改可预测、可追踪、可回退”。

  • 状态更新一律返回新对象/数组,禁用 .push().splice().assign() 等就地方法
  • 优先使用 [...arr, newItem]arr.filter()arr.map() 等返回新数组的方法
  • 复杂场景引入 immer:写法自然,自动保障不可变语义,且支持异步、Proxy、Draft 模式
  • 注意 MapSet:它们的方法(如 set()add())仍是就地修改,需手动包装成返回新实例的函数

真正的难点不在语法层面,而在于团队对“何时必须新建、何时可以复用”的共识。比如一个临时计算用的 filter 结果,没必要深克隆;但全局用户配置对象的每次更新,必须切断引用链。这个边界,比 constfreeze 更值得花时间定义。