JavaScript Object.freeze 为什么不能冻结嵌套对象?

Felix
Felix
发布时间:2026-09-24| 4人浏览过
原创

Object.freeze 只冻结传入的那一层对象。外层属性不能重新赋值,并不意味着该属性指向的嵌套对象也被锁住。排查配置被意外修改时,应该分别检查外层和内层,而不是只看一次 Object.isFrozen 的结果。

先区分“替换属性”和“修改内部值”

设 config.ui 指向一个对象。给 config.ui 重新赋值,是修改外层属性;给 config.ui.theme 赋值,是修改另一个对象的属性。freeze(config) 限制的是前者。对象引用没有改变,但引用对象中的数据变了,因此两种结果并不矛盾。

操作冻结外层后的行为
替换 config.ui严格模式下抛出 TypeError
修改 config.ui.theme内层未冻结时仍可修改
Object.isFrozen(config.ui)需单独检查,不能由外层结果推出
外层冻结 ≠ 嵌套对象冻结;执行 config.ui.theme = "dark" 后,theme 为 dark
执行 config.ui.theme = "dark" 后,theme 为 dark

先确认你想阻止哪一种修改

实际项目中的“配置不能变”通常包含两层要求:不能把配置里的某个属性替换掉,也不能修改该属性引用的内容。例如界面配置里有 ui 对象,业务代码既可能执行 config.ui = newUi,也可能直接调整 config.ui.theme。排查时应把这两条写操作分别列出来,而不是把它们统称为修改 config。

const 与 freeze 的作用位置也不同。const 约束变量绑定,让 config 不能再指向另一个对象;它不约束这个对象内部的字段。freeze 则作用于传入的对象,使本层已有的数据属性不能被重新赋值,同时不能新增或删除本层属性。因此 const config = Object.freeze(...) 仍然需要进一步检查嵌套对象。

这一区分直接影响代码评审:如果组件接收一个冻结过的配置,却仍能修改主题,不应立即判断冻结失效。先检查组件拿到的究竟是 config,还是 config.ui;再检查它修改的是外层引用还是内层字段。

运行一个能同时看到两种结果的例子

把下面代码保存为 freeze.cjs,用 node freeze.cjs 运行。使用严格模式,可以让失败的赋值明确报错;不要依赖非严格模式下的静默失败。

'use strict';
const assert = require('node:assert/strict');
const config = Object.freeze({ ui: { theme: 'light' } });
config.ui.theme = 'dark';
assert.equal(config.ui.theme, 'dark');
assert.equal(Object.isFrozen(config), true);
assert.equal(Object.isFrozen(config.ui), false);
try { config.ui = {}; } catch (error) { console.log(error.name); }
console.log(config.ui.theme);
console.log(Object.isFrozen(config.ui));

预期输出依次为 TypeError、dark、false。第一个结果说明外层限制生效;后两个说明嵌套对象仍然可变。

外层赋值报错,内层仍能修改
冻结外层对象后,替换 ui 会抛出 TypeError,但内层 theme 仍能改为 dark;false 表示 ui 尚未冻结。

逐行判断示例是否符合预期

示例先创建外层冻结、内层未冻结的配置,然后修改 theme。紧接着的断言验证 theme 确实变为 dark。两个 isFrozen 检查分别观察 config 和 config.ui,确认冻结范围只覆盖外层。最后尝试替换 ui,并捕获严格模式下的 TypeError,使程序能够继续打印后续结果。

如果你只看到 dark 而没有报错,应检查实际执行文件是否仍保留严格模式,以及报错的替换语句是否真的被执行。不要通过删掉异常语句让测试“通过”:测试的目的正是同时观察被禁止和被允许的两条路径。输出顺序与对应操作应一起保留,避免把 catch 中打印的错误名误认为程序未能运行。

根据数据结构选择冻结范围

对于层级明确、由自己创建的简单配置,可以先冻结 ui,再冻结 config。若配置层级不固定,递归冻结必须考虑循环引用和对象类型;不能把一个针对普通对象的例子直接套到所有内置对象上。也不要以为复制一份对象就等于冻结它:复制解决共享关系,冻结约束后续修改。

  • 仅需要限制变量重新赋值:使用 const。
  • 需要限制普通对象本层属性:使用 Object.freeze。
  • 需要不可变的嵌套配置:明确允许的数据类型,逐层处理并测试实际修改行为。

将检查落实到项目代码

对于只有两层的普通配置,先完成默认值合并,再冻结内层 ui,最后冻结外层 config。冻结之后,原先直接修改主题的调用方必须改变更新策略,例如创建包含新主题的新配置,再把新配置交给后续处理。不能一边要求配置不可变,一边继续依赖原地修改。

对于通用工具,不要只写一个递归函数就宣布支持所有对象。首先明确配置允许的类型,随后测试循环引用、重复引用和嵌套修改;涉及特殊对象时,分别验证它们的实际行为。验收至少包括:外层替换失败、指定内层修改失败、读取仍正常、错误路径可定位。这比只断言 Object.isFrozen(config) 更能证明约束满足需求。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。