JavaScript const vs. let
JavaScript const vs. let

JavaScript const vs. let

Published
Apr 26, 2023
Author
const 声明常量,初始化必须赋值,之后不能修改。let 声明变量,之后可以修改。对吗?那么什么是可修改,什么是不可修改呢?
考虑下面这段代码:
const person = { name: 'Alice', age: 18 }; person.age = 19; console.log(person.age); // => 19
person 使用 const 声明,确实被修改了,Why? 这是因为可修改和可赋值是两个不同的概念,之所以会得出最开始的结论,大多数都是用原始数据类型进行论证,造成了二者的混淆,例如:
const age = 18; age = 19; // error => 不可变,所以是常量 let name = 'Alice'; name = 'Bob'; // no error => 可变,所以是变量
这样得出的结论对吗? 对也不对,首先我们需要明确,什么是变量?什么是值?

值的类型

在 JavaScript 中,值可分为原始数据类型和引用类型两种。
  • 原始数据类型:Number、String、Boolean、Bigint、Symbol、Undefined 和 Null。
  • 引用类型:Object(Array、Function、Date、RegExp 等)。
原始数据类型的值不可修改,为什么不可修改?让我们尝试修改一下。
⚠️
下面这段代码是思想实验,并非真正的代码。
console.log(18); // => 18 18 = 19; console.log(18); // => 19
如果可以修改,18 将永远的消失,我们再也无法得到 18 真实的值,很显然这是灾难性的。这些原始值存在在 JavaScript 的宇宙中,我们随时可以从这个宇宙中得到我们想要的值。
引用类型的值是可以修改的,因为我们可以使用引用去获取到这些值,所以我们可以放心修改。
const person = { name: 'Alice', age: 18 }; person.age = 19;

什么是变量?

变量是值的代理人,当我们声明变量并赋值后,我们可以像使用这个值一样使用这个变量。当一个变量持有原始类型的值时,它拥有的是这个值本身,当变量持有引用类型的值时,它拥有的是指向该值的内存地址(也就是引用)。

可修改和可赋值

现在我们知道值和变量的关系,那么 constlet 声明的变量到底有什么区别,为什么最开始的结论会引起歧义,回到最开始的例子。
const person = { name: 'Alice', age: 18 }; person.age = 19; console.log(person.age); // => 19
const 限定了 person 变量不可被重新赋值,换言之,person 在初始化时持有的指向值的指针不可被修改,但是并不影响值本身可以被修改。
const person = { name: 'Alice', age: 18 }; person = {}; // TypeError: Assignment to constant variable.
如果对 person 重新进行赋值,则会发生错误。let 没有这样的限制,所以可以重新让变量持有另一个引用。
let person = { name: 'Alice', age: 18 }; person = {}; // no error
上面是变量持有引用类型的值的情况,产生混淆的根本原因发生在原始数据类型上,再回到之前的代码。
const age = 18; age = 19; // error => 不可变 let name = 'Alice'; name = 'Bob'; // no error => 可变
agename 都持有原始数据类型的值,原始数据类型的值无法被修改,此时 const 声明的 age 无法被重新赋值就意味着其持有的值无法被修改,let 声明的 name 可以被重新赋值,看起来就像是 name 可以被修改,其本质是被重新赋予了另外的值。
我们可以发现,constlet 就只有对变量能否被重新赋值的区别,其他的诸多不同都是由该区别导致的。例如:const 声明的变量需要初始化,let 不用初始化。既然 const 声明的变量之后不能重新赋值,那初始化时不赋值,这个变量也就没有实用意义,所以 let 可以不用赋值,后面需要时再进行赋值。

为什么偏向使用 const

ESlint 有一条 prefer-const 的规则,为什么会有这条规则存在?尽量使用 const 有什么好处吗?确实会有一些好处。
  1. 在一些比较长的代码中,对变量的重新赋值可能会导致意想不到的事情发生,特别是在闭包中,使用 const 可以让你明确知道该变量不会被修改(真的吗?),减少心智负担。
  1. 对初学者而言,他们可能会混淆 const 和不可变性之间的关系,使用 const 可以更早的了解可修改和赋值之间的关系。
  1. 在 React 中,从像 useState 这样的 hook 获取的值通常使用 const,这是因为这些值更像是 React 组件的参数,它们只能向一个方向流动。当你试图对齐进行重新赋值时会看到错误,有助于更早地了解 React 数据流。
  1. 有时候使用 const 声明的变量(或者叫它常量)会被 JS 引擎进行优化,因为其知道该变量不会被重新赋值,以此来提高执行效率。

确保引用类型数据不被修改

回到最开始的例子:
const person = { name: 'Alice', age: 18 }; person.age = 19; console.log(person.age); // => 19
或许你希望 person 既不可重新赋值,其拥有的值也不可被修改。可以使用 Object.freeze() 对对象进行冻结。
const person = { name: 'Alice', age: 18 }; Object.freeze(person); person.age = 19; // 严格模式报错 console.log(person.age); // => 18
这是属性值为原始数据类型的情况,如果属性值为引用类型,除非该值也被冻结,否则依然可以被修改,如果你想确保对象下的所有属性都无法更改,可以递归地对所有属性值进行冻结。

总结

const 声明的变量通常被称为常量,其在声明后无法被重新赋值,但是其持有的引用类型的值仍然可以被修改,let 声明的变量可以在声明后多次重新赋值。如果程序中需要该变量被重新赋值,则使用 let,不需要则使用 const
JavaScript 可以在你完全不了解此语言的情况下使用,又需要很长的时间才能正在掌握,即使写了快 4 年的 JavaScript,我仍然对语言某些方面不甚了解,希望可以通过不断地加深理解,最终可以掌握 JavaScript。