深冻结是针对多处引用且需防意外修改的场景(如全局状态机、只读schema)的防御性操作;出现多引用、嵌套层可写、引用相等失效或需固化原始数据时必须启用,而单作用域使用、typescript readonly约束到位、含特殊对象或临时dto时则无需。

深冻结不是“一上来就该做”的操作,而是有明确触发场景的防御性手段。关键看你的对象是否既被多处引用,又需绝对防止意外修改——比如全局状态机、配置快照、初始化后的只读 Schema、或跨组件共享的不可变数据源。
哪些情况必须考虑深冻结
以下信号出现任意一条,就该启动深冻结逻辑:
- 对象被多个函数、模块或组件直接持有引用(非拷贝),且你无法控制它们是否会调用
obj.nested.prop = xxx - 你依赖
Object.isFrozen()或开发者工具检查来验证不可变性,但发现嵌套层仍可写 - 使用了基于引用相等(
===)做性能优化的场景(如 React.memo、useMemo 依赖数组),却因深层属性被静默修改导致失效 - 后端返回的原始响应数据,在进入业务逻辑前需固化为“事实锚点”,避免后续任何中间处理污染原始结构
哪些情况其实不需要深冻结
过度冻结反而增加维护成本和运行时开销,这些情形可跳过:
- 对象仅在单个作用域内短暂使用,且无任何外部引用(例如局部计算中间值)
- 你已通过 TypeScript 的
readonly类型 + 严格 lint 规则(如@typescript-eslint/prefer-readonly)约束写法,且团队执行到位 - 对象含 Date、RegExp、Map、Set 或类实例——这些无法被
Object.freeze()安全处理,强行递归会跳过或报错,不如用结构化克隆或专用不可变封装 - 数据本身是临时 DTO 或即将被序列化(如发给后端),冻结对运行时安全无实质意义
如何快速判断是否已“真正冻结”
别只信 Object.isFrozen(obj),它只检测顶层。用这个小检查函数确认深度:
function isDeepFrozen(obj) {
if (!Object.isFrozen(obj)) return false;
for (const val of Object.values(obj)) {
if (typeof val === 'object' && val !== null && !isDeepFrozen(val)) {
return false;
}
}
return true;
}
对目标对象运行它。返回 false?说明某一层还能改——这就是深冻结的必要信号。
注意循环引用与性能边界
如果对象存在父子引用(如树节点带 parent 字段)、或层级超过 15 层,标准递归深冻结可能卡死或爆栈。此时必须引入 WeakMap 缓存已访问对象,并加 depth 限制(建议默认 ≤10)。不加防护的递归,不是保护数据,是在埋雷。










