Object.freeze()仅浅冻结对象第一层属性,不递归冻结嵌套对象;需配合深度冻结、不可变库或函数式更新才能实现真正不可变性。
object.freeze() 能让对象的属性不可添加、不可删除、不可修改(包括属性值和可枚举性、可配置性、可写性),但它**只做浅冻结**——即仅冻结对象自身第一层属性,不递归冻结嵌套对象。在纯函数式编程中,追求的是数据的完全不可变性,因此不能单靠 object.freeze() 就宣称实现了“不可变对象”,而需配合设计约束与辅助手段。
理解 freeze 的局限:它不等于深度不可变
例如:
const obj = { a: 1, nested: { b: 2 } };
Object.freeze(obj);
obj.a = 99; // ❌ 失败,a 不可改
obj.nested.b = 99; // ✅ 成功!nested 对象本身未被冻结
console.log(obj.nested.b); // 输出 99
这说明:若要真正支持函数式编程所需的不可变语义,必须确保所有嵌套层级都受保护。
合理使用 freeze 的实践方式
在实际项目中,Object.freeze() 更适合作为一种运行时防护 + 意图声明,而非唯一保障机制。推荐做法包括:
- 仅对明确设计为“一次性创建、永不变更”的简单配置对象或常量对象调用
Object.freeze(),如const COLORS = Object.freeze({ RED: '#f00', BLUE: '#00f' }); - 在工厂函数或构造器末尾自动冻结返回对象,形成约定俗成的“不可变构造”模式
- 配合 TypeScript 类型系统,用
readonly修饰符提前约束可读性,在编译期预防误改 - 避免在 freeze 后仍尝试修改对象——这不是 bug 修复手段,而是设计契约的一部分
替代或补充方案:真正需要深度不可变时怎么办
当业务逻辑涉及多层嵌套且要求强不可变性时,应转向更可靠的工具:
- 使用专门的不可变数据结构库,如
immer(提供可变语法但生成新副本)、immutable-js(已逐渐被现代替代)或rfc-794兼容的轻量方案 - 手动实现深度冻结函数(仅用于调试/开发环境,性能差、无法处理循环引用)
- 采用结构共享(structural sharing)思想,用
Object.assign({}, original)或展开运算符生成新对象,并在关键路径上冻结顶层结果(兼顾性能与语义) - 结合函数式更新模式:每次变更都返回全新对象,而非试图修改旧对象——这才是纯函数式的核心,freeze 只是锦上添花
小结:freeze 是信号,不是银弹
Object.freeze() 是 JavaScript 中表达“此对象不应被修改”这一意图最直接的方式,有助于提升代码可读性与协作效率。但它无法替代函数式编程中的核心原则:避免副作用、始终返回新值、将数据视为只读值。把它当作开发约定的一部分,而不是不可变性的技术兜底方案,才能真正服务于纯函数式风格。


















