const声明的变量并非真正不可变,它仅防止变量重新赋值,不阻止对象内部属性修改;对复合类型需配合object.freeze()或深克隆实现浅层或深层不可变。

const 声明的变量真的“不可变”吗?
不是。const 只锁定**绑定(binding)**,即变量名不能再指向另一个值;它不冻结值本身的内容。对原始类型(如 number、string、boolean)来说,效果接近“不可变”,因为它们值即存储内容;但对复合类型(Object、Array、Map 等),引用不变 ≠ 属性/元素不变。
为什么 const arr = [] 允许 arr.push(1)?
因为 arr 仍指向同一块内存地址,push 操作只是修改该对象内部状态,并未给 arr 重新赋值。这正是 const 的设计本意:防重赋值,不防内部变更。
常见错误现象:
- 误以为
const users = [{name: 'a'}]; users[0].name = 'b'会报错 —— 实际完全合法 - 在函数参数中用
const obj,却在函数体内修改obj.id = 2,自以为“安全”,实则副作用已发生
使用场景:
- 声明配置对象、缓存 Map、DOM 元素引用等需要长期持有引用但允许后续更新的变量
- 配合
Object.freeze()或深克隆实现真正不可变语义(见下节)
如何让 const + 对象真正“不可变”?
Object.freeze() 是最轻量的浅层冻结方案,但它只作用于第一层属性,且不递归。
const config = Object.freeze({
api: 'https://api.example.com',
timeout: 5000,
headers: { 'Content-Type': 'application/json' }
});
config.timeout = 10000; // 无效,静默失败(严格模式下报错)
config.headers['X-Trace'] = 'abc'; // ✅ 仍然成功!因为 headers 对象本身未被冻结
关键点:
-
Object.freeze()返回原对象,不创建新副本 - 仅冻结自有可枚举属性,不影响原型链上的属性访问
- 对嵌套对象需手动递归调用
Object.freeze(),或使用structuredClone()+ 冻结组合(注意兼容性) - 现代替代:用
immer库写“看似可变,实为不可变”的逻辑,或直接选用Immutable.js(已渐进淘汰)
const 在函数和模块顶层的特殊行为
顶层 const 声明具有“暂时性死区(TDZ)”,且不会被挂载到全局对象(如 window)上,这点和 var 截然不同。
容易踩的坑:
-
console.log(myConst); const myConst = 1;→ 报ReferenceError: Cannot access 'myConst' before initialization - 在模块中
const声明不会污染全局命名空间,但export后仍可通过导入访问 -
const不提升,也不支持重复声明,哪怕一个在函数内、一个在全局 —— 同名即冲突
性能影响几乎为零;V8 等引擎对 const 绑定有额外优化提示,但实际运行时与 let 差异极小。
真正复杂的是嵌套冻结边界和代理拦截的取舍 —— 你得明确自己要防的是哪一层的意外修改:是防止变量被重新赋值,还是防止数据结构被意外突变,或是防止深层副作用扩散。没想清楚这点,光用 const 就只是个心理安慰。










