Object.freeze()默认只浅冻结,无法防护嵌套对象;需手动递归实现深冻结,跳过原始值、内置对象及循环引用,并配合WeakMap防栈溢出。

JavaScript 中的 Object.freeze() 默认只做浅冻结 —— 它只冻结对象自身及其直接属性,对嵌套的对象、数组等引用类型内部结构完全不设防。要真正实现深层冻结(deep freeze),必须手动递归处理,并注意避开不可冻结的值和循环引用。
为什么浅冻结不够用
比如这个配置对象:
const config = {
api: "https://api.example.com",
headers: { "Content-Type": "application/json" },
timeout: 5000
};
执行 Object.freeze(config) 后:
-
config.timeout = 10000会失败(严格模式报错,非严格静默) -
config.newField = "x"添加属性也会失败 - 但
config.headers["Content-Type"] = "text/plain"依然成功 —— 因为headers对象本身没被冻结
手动递归实现深冻结
核心逻辑:对每个属性值判断是否为「可冻结的纯对象」,是则递归调用 Object.freeze()。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 只冻结 plain object 和 array(
Array.isArray()或typeof obj === 'object' && obj !== null) - 跳过
null、undefined、原始值(string/number/boolean/symbol 等) - 跳过内置对象如
Date、RegExp、Promise、Function(冻结无意义或报错) - 先冻结当前对象,再遍历其自有属性递归处理
简易版实现:
function deepFreeze(obj) {
if (obj == null || typeof obj !== 'object') return obj;
// 防止重复冻结(也避免部分内置对象报错)
if (Object.isFrozen(obj)) return obj;
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (typeof obj[prop] === 'object' && obj[prop] !== null && !Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return obj;
}
处理循环引用(进阶要点)
如果对象存在自引用或环状结构(比如 parent.children = [child],而 child.parent = parent),朴素递归会栈溢出。可靠做法是用 WeakMap 记录已处理对象:
- 每次进入递归前,检查该对象是否已在 WeakMap 中
- 若已存在,直接返回;否则存入并继续冻结
- WeakMap 不阻止垃圾回收,适合做临时缓存
示例片段:
const seen = new WeakMap();
function deepFreeze(obj) {
if (obj == null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return obj;
if (Object.isFrozen(obj)) return obj;
seen.set(obj, true);
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(key => {
const val = obj[key];
if (typeof val === 'object' && val !== null && !seen.has(val)) {
deepFreeze(val);
}
});
return obj;
}
实际使用注意事项
深冻结不是万能方案,需结合场景理性使用:
- 仅用于真正需要不可变语义的场景,比如全局配置、状态机初始值、常量定义
- 冻结后无法解冻,也无法在运行时动态更新 —— 这是设计使然,不是 bug
- 性能开销存在(尤其大嵌套结构),不建议对高频变动的数据结构反复调用
- 开发阶段可用
console.assert(Object.isFrozen(obj), 'obj should be frozen')辅助验证 - TypeScript 的
readonly是编译期检查,与Object.freeze()运行时防护互补,不是替代关系

















