在JavaScript
中,const
关键字用于声明常量。常量通常被认为是“不能改变的变量”:
1 | const hi = 5; |
但有趣的是,当使用const
创建一个对象时,我可以自由地改变它:
1 | const person = { |
为什么能改变person
变量呢?明明用的是const
!
为了理解这个明显的矛盾,我们需要了解赋值和变异之间的区别。这是JavaScript中的一个核心概念,当你清楚地理解了这个区别时,很多事情就会变得更加清晰。
作为标签的变量名
因此,这是一个完全有效的JavaScript
程序:
1 | 5; |
这是另一个例子:
1 | ['apple', 'banana', 'cherry']; |
在这两个例子中,正在创建一些东西。一个数字和一个数组。当代码运行时,这些数据将被创建并存储在计算机的内存中。
变量允许我们在我们创建的东西上贴上标签,这样我们稍后可以引用它:
1 | // 现在创建它... |
代码执行顺序是:数组首先被创建,然后我们将我们的fruits
标签指向它。
重新分配标签
当我们使用let
关键字创建一个变量时,我们可以改变标签所指向的“东西”。
例如,我们可以将我们的fruits
标签指向一个新值:
1 | let fruits = ['apple', 'banana', 'cherry']; |
这被称为重新分配。fruits
标签应该指向一个完全不同的值:
1 | // 我们从一个有标签的数组开始: |
我们并没有修改数据,而是改了标签。我们将其从原始数组分离出来,并连接到一个新数组上。
相比之下,用const
创建的变量不能被重新分配:
1 | const fruits = ['apple', 'banana', 'cherry']; |
这是let
和const
之间的基本区别。当我们使用const
时,我们创建了一个不可摧毁的链接,将变量名和数据片段联系在一起。
然而,事实是:我们仍然允许修改数据本身!只要标签保持不变。
例如,对于数组,我们可以添加/删除其中的项目而不会有问题。fruits
变量仍然连接到同一个数组上:
1 | const fruits = ['apple']; |
这被称为变异。我们通过添加/删除项目来编辑数组的值。
再举一个例子,使用对象而不是数组。我们可以编辑对象内的键/值,只要标签仍然指向同一个对象:
1 | const event = { |
重新分配(将变量名指向新的东西)和变异(编辑东西内部的数据)之间有一个基本的区别。
当我们使用const
创建一个常量时,我们可以百分之百确定该变量永远不会被重新分配,但是在变异方面则无法保证。const
根本不阻止变异。
这里还有一个更复杂的地方:“原始”数据类型如字符串和数字是不可变的。这使得事情变得更加混乱。
冻结对象和数组
const
关键字不能保护我们免受变异的影响。有没有其他方法可以确保我们的数据不会被编辑呢?
当然有!这就是一个非常方便的方法,称为Object.freeze()
:
1 | // 对于数组: |
Object.freeze()
可以用于对象和数组,防止所有形式的变异。唯一的“问题”是它是浅冻结的;它不会冻结任何嵌套的对象/数组。
另外,如果你使用TypeScript
,你可以使用as const
断言来实现类似的效果:
1 | const arr = [1, 2, 3] as const; |
像所有静态类型一样,这些保护在编译为JavaScript
时会消失,因此这并不完全像Object.freeze()
提供的保护那样可靠。不过,根据你的使用情况,这可能已经足够了!
原始数据类型
到目前为止,我们看到的所有例子都涉及对象和数组。但是如果我们有一个“原始”数据类型,比如字符串、数字或布尔值,该怎么办呢?
让我们以数字为例:
1 | let age = 36; |
我们应该如何解释这个呢?我们是重新分配age
标签给一个新值,还是在这个数字上进行变异,将36
编辑为37
呢?
事实是:JavaScript
中的所有原始数据类型都是不可变的。我们无法“编辑”一个数字的值。我们只能将变量重新分配给不同的值。
假设有一个包含所有可能数字的大列表。把age
变量分配给数字36
,我们可以指向列表中的任何其他数字:
这些数字本身是无法被改变的。我们只能改变标签指向的数字。
对于包括字符串、布尔值、null等在内的所有原始值类型,都是如此。
一个思维实验
如前所述,JavaScript中的原始值是“不可变”的;它们无法被编辑。
但如果它们能被编辑会怎么样?如果数字本身可以发生变异,语法会是什么样子呢?
看起来会像这样:
1 | // 编辑数字36的值: |
“变异”的整个概念是基本上改变了那个值。当我们变异一个对象时,我们改变了那个对象的“本质”,当我们引用它们时可以看到这些变化:
1 | const me = { age: 36 }; |
因此,如果我们能够在JavaScript中变异原始值,这基本上意味着重写某些数字,以至于它们再也无法被引用!
这显然会令人困惑和无益,这就是为什么JavaScript
中的原始值是不可变的原因。