Object.freeze() 仅浅冻结对象,嵌套对象仍可修改;深冻结需手动递归实现,注意跳过null、原始值、函数并用WeakMap防循环引用;preventExtensions、seal、freeze防篡改强度逐级增强但均不递归;生产中应慎用freeze,仅适用于配置常量等极少数场景。

Object.freeze() 能否真正阻止对象被篡改
Object.freeze() 是 JavaScript 中最直接的防篡改手段,但它只做浅冻结:顶层属性不可增删、不可重配置、不可重新赋值,但嵌套对象仍可修改。比如 obj.user.name = 'bob' 在 obj 被 freeze 后依然生效——只要 obj.user 本身没被冻结。
深层冻结需要递归调用 Object.freeze()
没有内置的深冻结 API,必须手动递归处理。常见错误是忽略 null、原始值、函数、Symbol 键或循环引用。
- 只对
typeof value === 'object'且value !== null的值递归 - 跳过函数(
typeof value === 'function'),因为冻结函数无意义且可能报错 - 用
WeakMap记录已处理对象,避免无限递归 -
Object.getOwnPropertyNames()+Object.getOwnPropertySymbols()才能覆盖所有键
function deepFreeze(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date || obj instanceof RegExp) return obj;
if (typeof obj === 'function') return obj;
const seen = new WeakMap();
function _freeze(o) {
if (o === null || typeof o !== 'object') return o;
if (seen.has(o)) return o;
seen.set(o, true);
Object.getOwnPropertyNames(o).forEach(prop => {
_freeze(o[prop]);
});
Object.getOwnPropertySymbols(o).forEach(sym => {
_freeze(o[sym]);
});
return Object.freeze(o);
}
return _freeze(obj);
}
Object.seal() 和 Object.preventExtensions() 的区别在哪
三者层级不同:preventExtensions() 最轻(仅禁止新增属性),seal() 中等(禁止新增 + 禁止删除/重配置已有属性),freeze() 最强(在 seal 基础上还禁止修改属性值)。但三者都只作用于自身,不递归。
-
Object.preventExtensions(obj):还能改值、删属性 -
Object.seal(obj):不能删、不能改configurable,但能改值(如果writable: true) -
Object.freeze(obj):默认把所有自有属性设为writable: false,所以值也不能改
生产环境慎用 freeze 的真实代价
冻结操作本身开销不大,但副作用明显:V8 引擎会将冻结对象标记为“不可变”,后续对其属性访问可能绕过某些优化路径;更关键的是,冻结后无法再用 Object.assign()、解构赋值或任何方式更新字段,调试时 console.log 查看可能显示 [object Object] 而非可展开结构,且 Chrome DevTools 里属性名会变灰、带锁图标,容易误判为“数据没加载出来”。
立即学习“Java免费学习笔记(深入)”;
真正需要冻结的场景其实很窄:配置常量、枚举定义、导出的不可变 schema 描述。日常状态对象用 const 声明 + 不主动重赋值,比盲目 freeze 更实际。











