const object不生效

JavaScript中,const关键字用于声明常量。常量通常被认为是“不能改变的变量”:

1
2
3
4
5
const hi = 5;
hi = 10;
// 🛑 Uncaught TypeError: Assignment to constant variable.
console.log(hi);
// -> 5

但有趣的是,当使用const创建一个对象时,我可以自由地改变它:

1
2
3
4
5
6
7
const person = {
name: 'Hassan',
};
person.name = 'Sujata';
// 看起来可以工作? 🤔
console.log(person);
// -> { name: 'Sujata' }

为什么能改变person变量呢?明明用的是const

为了理解这个明显的矛盾,我们需要了解赋值和变异之间的区别。这是JavaScript中的一个核心概念,当你清楚地理解了这个区别时,很多事情就会变得更加清晰。

作为标签的变量名

因此,这是一个完全有效的JavaScript程序:

1
5;

这是另一个例子:

1
['apple', 'banana', 'cherry'];

在这两个例子中,正在创建一些东西。一个数字和一个数组。当代码运行时,这些数据将被创建并存储在计算机的内存中。

变量允许我们在我们创建的东西上贴上标签,这样我们稍后可以引用它:

1
2
3
4
5
// 现在创建它...
const fruits = ['apple', 'banana', 'cherry'];
// ...稍后访问它:
console.log(fruits);
// -> ['apple', 'banana', 'cherry']

代码执行顺序是:数组首先被创建,然后我们将我们的fruits标签指向它。

重新分配标签

当我们使用let关键字创建一个变量时,我们可以改变标签所指向的“东西”。

例如,我们可以将我们的fruits标签指向一个新值:

1
2
3
let fruits = ['apple', 'banana', 'cherry'];
// ⚠️⚠️⚠️⚠️
// 从上面的列表中选择一个不同的选项

这被称为重新分配。fruits标签应该指向一个完全不同的值:

1
2
// 我们从一个有标签的数组开始:
let fruits = ['apple', 'banana', 'cherry'];

我们并没有修改数据,而是改了标签。我们将其从原始数组分离出来,并连接到一个新数组上。

相比之下,用const创建的变量不能被重新分配:

1
const fruits = ['apple', 'banana', 'cherry'];

这是letconst之间的基本区别。当我们使用const时,我们创建了一个不可摧毁的链接,将变量名和数据片段联系在一起。

然而,事实是:我们仍然允许修改数据本身!只要标签保持不变。

例如,对于数组,我们可以添加/删除其中的项目而不会有问题。fruits变量仍然连接到同一个数组上:

1
const fruits = ['apple'];


这被称为变异。我们通过添加/删除项目来编辑数组的值。

再举一个例子,使用对象而不是数组。我们可以编辑对象内的键/值,只要标签仍然指向同一个对象:

1
2
3
4
5
6
const event = {
title: 'Change me!',
startsAt: '2023-05-29T16:00:00Z',
duration: 4,
confirmed: true,
};

重新分配(将变量名指向新的东西)和变异(编辑东西内部的数据)之间有一个基本的区别。

当我们使用const创建一个常量时,我们可以百分之百确定该变量永远不会被重新分配,但是在变异方面则无法保证。const根本不阻止变异。

这里还有一个更复杂的地方:“原始”数据类型如字符串和数字是不可变的。这使得事情变得更加混乱。

冻结对象和数组

const关键字不能保护我们免受变异的影响。有没有其他方法可以确保我们的数据不会被编辑呢?

当然有!这就是一个非常方便的方法,称为Object.freeze()

1
2
3
4
5
6
7
8
9
10
11
// 对于数组:
const arr = Object.freeze([1, 2, 3]);
arr.push(4);
console.log(arr);
// -> [1, 2, 3]

// 对于对象:
const person = Object.freeze({ name: 'Hassan' });
person.name = 'Sujata';
console.log(person);
// -> { name: 'Hassan' }

Object.freeze() 可以用于对象和数组,防止所有形式的变异。唯一的“问题”是它是浅冻结的;它不会冻结任何嵌套的对象/数组。

另外,如果你使用TypeScript,你可以使用as const断言来实现类似的效果:

1
2
3
const arr = [1, 2, 3] as const;
arr.push(4);
// 🛑 错误:属性 'push' 在类型 'readonly [1, 2, 3]' 上不存在。

像所有静态类型一样,这些保护在编译为JavaScript时会消失,因此这并不完全像Object.freeze()提供的保护那样可靠。不过,根据你的使用情况,这可能已经足够了!

原始数据类型

到目前为止,我们看到的所有例子都涉及对象和数组。但是如果我们有一个“原始”数据类型,比如字符串、数字或布尔值,该怎么办呢?

让我们以数字为例:

1
2
let age = 36;
age = 37;

我们应该如何解释这个呢?我们是重新分配age标签给一个新值,还是在这个数字上进行变异,将36编辑为37呢?

事实是:JavaScript中的所有原始数据类型都是不可变的。我们无法“编辑”一个数字的值。我们只能将变量重新分配给不同的值。

假设有一个包含所有可能数字的大列表。把age变量分配给数字36,我们可以指向列表中的任何其他数字:


这些数字本身是无法被改变的。我们只能改变标签指向的数字。

对于包括字符串、布尔值、null等在内的所有原始值类型,都是如此。

一个思维实验

如前所述,JavaScript中的原始值是“不可变”的;它们无法被编辑。

但如果它们能被编辑会怎么样?如果数字本身可以发生变异,语法会是什么样子呢?

看起来会像这样:

1
2
3
4
// 编辑数字36的值:
36 = 37;
// 数字36不再存在!
console.log(36); // 37

“变异”的整个概念是基本上改变了那个值。当我们变异一个对象时,我们改变了那个对象的“本质”,当我们引用它们时可以看到这些变化:

1
2
3
4
const me = { age: 36 };
me.age = 37;
console.log(me);
// -> { age: 37 }

因此,如果我们能够在JavaScript中变异原始值,这基本上意味着重写某些数字,以至于它们再也无法被引用!

这显然会令人困惑和无益,这就是为什么JavaScript中的原始值是不可变的原因。