Object.freeze()仅浅冻结,深度冻结需递归处理嵌套对象和数组;deepFreeze函数通过Reflect.ownKeys获取所有自有属性(含Symbol),对每个引用类型值递归冻结后再冻结自身,确保全局配置不可变。

Object.freeze() 本身只做浅冻结,要实现深度不可变(Deep Freeze),需递归冻结所有嵌套对象和数组。这是保护全局配置快照的关键——防止运行时意外修改,尤其在多模块共享配置的场景中。
为什么 Object.freeze() 默认不是深度的
它仅冻结目标对象自身的属性(设置 writable: false、configurable: false,并阻止添加新属性),但对属性值为对象或数组的情况不做处理。例如:
const config = { api: { url: 'https://a.com' }, timeout: 5000 };
Object.freeze(config);
config.api.url = 'https://b.com'; // ✅ 仍能修改!
手写 deepFreeze 实现原理
核心是递归遍历所有可枚举自有属性,对每个引用类型值(object/array)先 deepFreeze 再 freeze 自身:
- 跳过 null、原始类型(string/number/boolean/symbol/bigint)和不可扩展对象(已冻结或密封)
- 用 Object.getOwnPropertyNames() 获取所有自有属性名(含不可枚举但非 Symbol 的)
- 对每个属性值,若为 object 且未被冻结,递归调用 deepFreeze
- 最后调用 Object.freeze(obj) 锁定当前层级
安全可靠的 deepFreeze 函数示例
以下实现兼顾健壮性与兼容性(支持 Symbol 属性、避免循环引用崩溃):
function deepFreeze(obj) {
if (obj === null || typeof obj !== 'object') return obj;
// 防止重复冻结或冻结内置对象(如 Date、RegExp)
if (Object.isFrozen(obj)) return obj;
// 冻结自身所有自有属性(含 Symbol)
const props = Reflect.ownKeys(obj);
for (const prop of props) {
const val = obj[prop];
if (val !== null && typeof val === 'object') {
deepFreeze(val); // 递归冻结子对象
}
}
return Object.freeze(obj);
}
// 使用示例
const config = {
api: { baseUrl: 'https://api.example.com', version: 'v1' },
features: ['auth', 'logging'],
debug: true
};
deepFreeze(config);
config.api.baseUrl = 'hacked'; // ❌ TypeError(严格模式下)
config.features.push('new'); // ❌ TypeError
实际应用中的注意事项
- 冻结时机很重要:应在配置加载完成、校验通过后立即 deepFreeze,避免中间状态被修改
- 冻结后无法解冻:JavaScript 没有 deepUnfreeze,如有动态更新需求,应生成新快照而非修改旧对象
- Symbol 属性需显式处理:使用 Reflect.ownKeys() 而非 Object.keys() 或 getOwnPropertyNames(),确保 Symbol 属性也被遍历
- 注意 Proxy 和 WeakMap 等边界情况:deepFreeze 不影响 Proxy 行为;WeakMap 的键不受影响,因其不暴露引用
不复杂但容易忽略:深冻结不是一劳永逸,关键在于冻结动作是否覆盖全部配置路径,以及是否在正确生命周期节点执行。

















