Object.freeze仅浅冻结对象,不递归冻结嵌套对象、数组或原型链属性;深度冻结需手动递归实现,或结合const声明、ES模块、TypeScript readonly等多重防护。

Object.freeze 只能浅冻结对象,不能“彻底”冻结嵌套对象 —— 这是关键前提。所谓“彻底冻结”,需手动递归冻结所有层级的属性,或借助其他策略规避修改风险。
为什么 Object.freeze 不是“彻底”的?
它只冻结对象自身的可枚举属性(即第一层),不处理嵌套对象、数组或原型链上的属性:
- 若对象有个属性值是另一个对象,该子对象仍可被修改;
- 数组被 freeze 后,length 和元素仍可改(除非也对数组本身 freeze);
- 无法阻止通过 Object.setPrototypeOf 修改原型,也无法禁用 delete 操作(freeze 本身已禁止 delete,但深层对象没被 freeze 就仍可删其 own 属性)。
实现“深度冻结”的简单递归函数
对每个属性值,如果是对象(含数组)且未被冻结,就递归调用 freeze:
function deepFreeze(obj) {
if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
Object.getOwnPropertyNames(obj).forEach(prop => {
deepFreeze(obj[prop]);
});
return Object.freeze(obj);
}
return obj;
}
// 使用示例
const config = {
api: { baseUrl: 'https://api.example.com', timeout: 5000 },
features: ['darkMode', 'i18n'],
version: '2.1.0'
};
deepFreeze(config);
config.api.baseUrl = 'hacked'; // 无效,已冻结
config.features.push('analytics'); // 无效,数组也被冻结
更稳妥的常量定义方式(推荐生产环境)
比起运行时冻结,更可靠的做法是结合语言机制与约定:
- 用 const 声明 + 字面量 + 深度冻结函数,确保引用和内容都不变;
- 对敏感配置,考虑用 ES 模块导出只读对象(模块顶层 const + freeze),利用模块缓存和不可重赋值特性;
- 如需强约束,可用 TypeScript 的 readonly(编译期检查)配合 freeze(运行时防护),形成双重保障;
- 避免在冻结后还试图修改——冻结不是调试工具,而是防御性实践,应在初始化完成、确认无误后再调用。
注意边界情况
以下操作仍可能绕过或破坏冻结效果,需提前规避:
- 冻结前已有引用指向子对象:外部仍可通过那个引用修改(freeze 不影响已有引用);
- Date、RegExp、Map、Set 等内置对象实例,freeze 只冻结其自身属性,不封印内部状态(例如 Date.prototype.setTime 仍可调);
- 使用 Proxy 包裹冻结对象后,get/set 陷阱可能掩盖冻结行为,慎用。











