const 的真相:值不可变 ≠ 属性不可变 —— JavaScript 中的“常量”到底能不能改值?

268 阅读4分钟

引言

在 ES5 时代,JavaScript 没有真正的常量声明方式,开发者只能通过var配合命名规范(如全大写)模拟常量,但是var关键字在JavaScript中会导致一些理解和使用上的歧义。

const 是 ES6(ECMAScript 2015)引入的重要特性之一。它被广泛用于声明常量变量,但很多初学者甚至一些有经验的开发者,对 const 的行为仍存在误解。

特别是当面对对象时,很多人会问:“const 声明的对象,为什么属性还能改?

本文将从 const 的本质,阐述它在不同数据类型中的表现,并澄清一个核心问题:const 真的能加值吗?或者说,const 赋值后值真的不能改变吗?

一、const 的基本含义:绑定不可变

const 的作用是声明一个一旦赋值就不能再被重新赋值的变量引用。也就是说:

const PI = 3.14;
PI = 3.1415; // 报错:Assignment to constant variable.

这是对基本数据类型(如 number、string、boolean、null、undefined、symbol)而言的典型行为。

但是,当我们使用 const 声明一个复杂数据类型(如对象或数组)时,情况就有所不同了。

二、对象的“值”和“引用”:理解 const 的关键

来看一个例子:

const person = { name: 'Alice' };
person.name = 'Bob'; // 合法!没有报错
console.log(person); // 输出 { name: 'Bob' }

person = {}; // 报错:Assignment to constant variable.

你会发现,尽管我们使用了 const,但对象内部的属性是可以修改的。这是因为在 JavaScript 中:

  • 基本类型的数据直接存储在栈中,保存的是值本身。
  • 复杂类型的变量存储的是指向堆内存的引用地址。

image.png

再来看一个例子:

const box = [
    { goods: "手机", price: "1000" },
    { goods: "电脑", price: "2000" },
]
const newbox = box;
newbox.push({ goods: "鼠标", price: "100" });
console.log(box); // 输出 [{ goods: "手机", price: "1000" }, { goods: "电脑", price: "2000" }, { goods: "鼠标", price: "100" }]
console.log(newbox); // 输出同上
  • 当我们通过 newbox.push() 方法向数组添加新元素时,由于 newbox 和 box 实际上指向的是同一个堆内存地址,所以对 newbox 的任何修改都会反映在 box 上。
  • 最终打印出的 box 和 newbox 都包含了新增的商品信息,证明了虽然 const 确保了变量名与内存地址之间的绑定关系不变,但并不限制你对该内存地址中内容的修改。

因此,const 实际上保证的是变量名与内存地址之间的绑定关系不变,而不是该内存地址所指向的内容不能变化。

三、通俗理解:const 是“指针锁”,不是“内容锁”

你可以把 const 想象成一个“保险柜的钥匙只能用一次”的机制:

  • 第一次放入东西后,不能再替换这个保险柜。
  • 但如果你打开的是一个盒子,里面的东西还是可以调整的。

所以结论就是:

✅ 使用 const 声明对象时,对象的引用地址不可变,但其内部属性可以修改

四、那如果我想让对象也不能改呢?

如果你希望一个对象在赋值之后,其所有属性都不能被修改,你可以结合使用 Object.freeze() 方法:

const user = Object.freeze({ name: 'Tom' });
user.name = 'Jerry'; // 不会生效,在严格模式下会抛出错误
console.log(user); // 输出 { name: 'Tom' }

需要注意的是:

  • Object.freeze() 只会冻结当前层级的属性(即浅冻结),不会递归冻结嵌套对象。
  • 如果需要深度冻结,需要手动实现递归逻辑。

五、const 的优势:代码更安全、可读性更强

虽然 const 并不真正“禁止一切修改”,但它确实带来了以下好处:

✅ 提升代码可读性和意图表达

使用 const 明确告诉其他开发者:这个变量不应该被重新赋值。这是一种良好的编码规范。

✅ 防止意外重写变量值

尤其是在函数内部或者大型项目中,避免变量被误操作修改。

✅ 支持块级作用域,减少 bug

相对于 ES5 中的 varconstlet 支持块级作用域(block scope),避免了变量提升带来的潜在问题。

✅ 不污染全局命名空间

使用 const 声明的变量不会自动挂载到全局对象(如浏览器的 window)上,从而降低了全局污染的风险。

六、总结:const 能加值吗?

类型是否能修改值是否能修改引用
基本类型(number、string 等)❌ 不可修改❌ 不可修改
对象 / 数组✅ 可以修改属性或元素❌ 不可修改引用

所以我们可以得出一个清晰的结论:

📌 const 所谓的“值不可变”,实际上指的是引用地址不可变,而不是对象内容不可变


💡 开发建议

  • 优先使用 const,其次使用 let,尽量避免使用 var
  • 当你需要一个完全不可变的对象时,记得使用 Object.freeze()
  • 理解 const 的真正含义,有助于写出更健壮、更易维护的 JavaScript 代码。

🧠 小知识:

  • 基本类型:值存在栈中,const 控制值本身;
  • 复杂类型:值存在堆中,栈中只是引用地址,const 控制的是地址而非堆中的内容。

📌 记住一句话:const 加的是变量引用的“锁”,而不是对象内容的“锁”。