const声明的变量还能修改?原理都在这了
前言
const
关键字用于声明只读的常量,一旦被赋值,其值就不能再被改变。但是,这并不意味着该变量持有的值是不可变的。
本文将结合案例逐步探讨JavaScript中const
声明的工作原理,解释为何这些看似常量的变量却可以被修改,并探讨其实际应用。
耐心看完,你一定有所收获。
正文
const
关键字用于声明一个变量,该变量的值在其生命周期中不会被重新赋值。
现象
1. 基本数据类型
对于基本数据类型(如数字、字符串、布尔值),const
确保变量的值不会改变。
const num = 42;
// num = 43; // 这会抛出错误
2. 对象
对于对象,仍然可以修改对象的属性,但不能重新赋值整个对象。
const girlfriend = {
name: "小宝贝"
};
girlfriend.name = "亲爱的"; // 这是允许的,因为你只是修改了对象的一个属性
// girlfriend = { name: "亲爱的" }; // 这会抛出错误,因为你试图改变obj的引用
假如你有个女朋友,也许并没有,但我们可以假设,她的名字或者你平时叫她的昵称是"小宝贝"。
有一天,你心血来潮,想换个方式叫她,于是叫她"亲爱的"。这完全没问题,因为你只是给她换了个昵称,她本人并没有变。
但是,如果有一天你看到另一个女生,你却说:“哎,这不是亲爱的吗?”这就出大问题了!因为你把一个完全不同的人当成了你的女朋友。
这就像你试图改变girlfriend
的引用,把它指向了一个新的对象。
JavaScript不允许这样做,因为你之前已经明确地告诉它,girlfriend
就是那个你叫"小宝贝"的女朋友,你不能突然把另一个人说成她。
简单来说,你可以随时给你的女朋友起个新昵称,但你不能随便把别的女生当成你的女朋友。
3. 数组
对于数组,你可以修改、添加或删除元素,但不能重新赋值整个数组。
const arr = [1, 2, 3];
arr[0] = 4; // 这是允许的,因为你只是修改了数组的一个元素
arr.push(5); // 这也是允许的,因为你只是向数组添加了一个元素
// arr = [6, 7, 8]; // 这会抛出一个错误,因为你试图改变arr的引用
假设arr
是你的超市购物袋,里面有三个苹果,分别标记为1、2和3。
你检查了第一个苹果,觉得它不够新鲜,所以你把它替换成了一个新的苹果,标记为4。这就像你修改数组的一个元素。这完全可以,因为你只是替换了袋子里的一个苹果。
后来,你决定再放一个苹果进去,标记为5。这也没问题,因为你只是向袋子里添加了一个苹果。
苹果再变,袋子仍然是原来的袋子。
但是,当你试图拿个新的装着6、7、8的购物袋来代替你原来的袋子时,就不对了。你不能拿了一袋子苹果,又扔在那不管,反而又去拿了一袋新的苹果。
你礼貌吗?
你可以随时替换袋里的苹果或者放更多的苹果进去,但你不能拿了一袋不要了又拿一袋。
原理
在JavaScript中,const
并不是让变量的值变得不可变,而是让变量指向的内存地址不可变。换句话说,使用const
声明的变量不能被重新赋值,但是其所指向的内存中的数据是可以被修改的。
使用const
后,实际上是确保该变量的引用地址不变,而不是其内容。
结合上面两个案例,女朋友和购物袋就好比是内存地址,女朋友的外号可以改,但女朋友是换不了的,同理袋里装的东西可以换,但袋子仍然是那个袋子。
当使用const
声明一个变量并赋值为一个对象或数组,这个变量实际上存储的是这个对象或数组在内存中的地址,形如0x00ABCDEF
(这只是一个示例地址,实际地址会有所不同),而不是它的内容。这就是为什么我们说变量“引用”了这个对象或数组。
实际应用
这种看似矛盾的特性实际上在开发中经常用到。
例如,在开发过程中,可能希望保持一个对象的引用不变,同时允许修改对象的属性。这可以通过使用const
来实现。
考虑以下示例:
假设你正在开发一个应用,该应用允许用户自定义一些配置设置。当用户首次登录时,你可能会为他们提供一组默认的配置。但随着时间的推移,用户可能会更改某些配置。
// 默认配置
const userSettings = {
theme: "light", // 主题颜色
notifications: true, // 是否开启通知
language: "en" // 默认语言
};
// 在某个时间点,用户决定更改主题颜色和语言
function updateUserSettings(newTheme, newLanguage) {
userSettings.theme = newTheme;
userSettings.language = newLanguage;
}
// 用户调用函数,将主题更改为"dark",语言更改为"zh"
updateUserSettings("dark", "zh");
console.log(userSettings); // 输出:{ theme: "dark", notifications: true, language: "zh" }
在这个例子中,我们首先定义了一个userSettings
对象,它包含了用户的默认配置。尽管我们使用const
来声明这个对象,但我们仍然可以随后更改其属性来反映用户的新配置。
这种模式在实际开发中很有用,因为它允许我们确保userSettings
始终指向同一个对象(即我们不会意外地将其指向另一个对象),同时还能够灵活地更新该对象的内容以反映用户的选择。
为什么不用let
以上所以案例中,使用let都是可行,但它的语义和用途相对不同,主要从这几个方面进行考虑:
- 不变性:使用
const
声明的变量意味着你不打算重新为该变量赋值。这为其他开发人员提供了一个明确的信号,即该变量的引用不会改变。在上述例子中,我们不打算将userSettings
重新赋值为另一个对象,我们只是修改其属性。因此,使用const
可以更好地传达这一意图。 - 错误预防:使用
const
可以防止意外地重新赋值给变量。如果你试图为const
变量重新赋值,JavaScript会抛出错误。这可以帮助捕获潜在的错误,特别是在大型项目或团队合作中。 - 代码清晰度:对于那些只读取和修改对象属性而不重新赋值的场景,使用
const
可以提高代码的清晰度,可以提醒看到这段代码的人:“这个变量的引用是不变的,但其内容可能会变。”
一般我们默认使用const
,除非确定需要重新赋值,这时再考虑使用let
。这种方法旨在鼓励不变性,并使代码更加可预测和易于维护。
避免修改
如果我们想要避免修改const
声明的变量,当然也是可以的。
例如,我们可以使用浅拷贝来创建一个具有相同内容的新对象或数组,从而避免直接修改原始对象或数组。这可以通过以下方式实现:
const originalArray = [1, 2, 3];
const newArray = [...originalArray]; // 创建一个原始数组的浅拷贝
newArray.push(4); // 不会影响原始数组
console.log(originalArray); // 输出: [1, 2, 3]
console.log(newArray); // 输出: [1, 2, 3, 4]
总结
const
声明的变量之所以看似可以被修改,是因为const
限制的是变量指向的内存地址的改变,而不是内存中数据的改变。这种特性在实际开发中有其应用场景,允许我们保持引用不变,同时修改数据内容。
然而,如果我们确实需要避免修改数据内容,可以采取适当的措施,如浅拷贝。