object.freeze()仅提供浅层冻结,需结合浅拷贝(如展开运算符)构建安全快照;它冻结对象自身属性但不递归冻结嵌套对象,适用于配置、初始状态等场景。

直接用 Object.freeze() 冻结一个对象,只能阻止对它的**直接属性**进行增、删、改,但无法阻止嵌套对象内部的修改——也就是说,它只提供浅层写保护。要构建真正具备“写保护”特征的原始快照,关键在于:先做**结构上可预测的浅拷贝**(如用展开运算符或 Object.assign),再对**每一层可枚举属性值**递归冻结(或至少冻结顶层 + 明确约束使用方式)。实际中更推荐“冻结 + 浅拷贝”组合用于**不可变快照建模**,而非试图完全深冻结。
明确 freeze 的作用边界:它不递归,也不影响引用目标
Object.freeze(obj) 仅冻结 obj 自身的属性描述符(writable、configurable、enumerable),并防止新增/删除属性。但它不会冻结 obj 属性值所指向的其他对象。例如:
const source = { a: 1, nested: { b: 2 } };
const snapshot = Object.freeze({ ...source });
snapshot.a = 99; // ❌ 失败(被冻结)
snapshot.nested.b = 99; // ✅ 成功!nested 对象本身未被冻结
用浅拷贝 + freeze 构建“安全快照”的典型模式
适用于配置对象、初始状态、API 响应缓存等场景——你信任数据结构扁平,或只需保证顶层不可篡改。操作分两步:
- 用展开运算符
{...obj}或Object.assign({}, obj)创建新对象(避免引用原对象) - 立即调用
Object.freeze()锁定该副本 - (可选)对已知的嵌套对象属性单独 freeze,如
snapshot.nested = Object.freeze({...snapshot.nested})
这样得到的 snapshot 具备两个关键特征:一是与原对象解耦,二是顶层属性写操作会静默失败(严格模式下抛错),便于早期发现误修改。
配合 Object.is() 或 === 进行快照比对
冻结后的对象引用不变,适合用 === 快速判断是否仍为同一快照;若需检测内容是否变化,可结合浅比较工具(如 Lodash 的 _.isEqual)或自定义逻辑。注意:Object.is(freeze(a), freeze(a)) 仍为 true,因为两次 freeze 返回的是不同对象实例——重点是“你持有的那个 frozen 引用没变”。
替代思路:用 class 封装 + getter-only 接口
当需要更强的封装性与类型提示时,可将快照建模为类:
class Snapshot {
constructor(data) {
Object.assign(this, JSON.parse(JSON.stringify(data))); // 深拷贝基础值
Object.freeze(this);
}
get a() { return this._a; }
get nested() { return this._nested; }
}
这种方式把“不可变性”从运行时约束(freeze)延伸到设计意图(只读 getter),更适合长期维护的业务模型。










