三个关键字,一个用途
JavaScript 有三种声明变量的方式:var、let 和 const。它们都是把一个名字绑定到一个值上,但在作用域、重新赋值的规则,以及声明语句运行之前的行为上各不相同。现代代码大多数时候用 const,值需要改变时用 let,而 var 很少用,甚至完全不用。
简单来说:
本页剩下的内容,就是解释这三行为什么会这样表现。
const:默认选择
const 创建一个不能重新赋值的绑定。一旦写了 const x = 5,之后就不能再写 x = 6,引擎会抛出 TypeError。
规则就这么一条。因为在写得好的程序中,大多数变量都不需要重新赋值,所以 const 适用于绝大多数情况。优先使用 const,能让那些值确实会改变的地方更加醒目。
一个常见的困惑:const 保护的是绑定,而不是值。如果值是对象或数组,它的内容仍然是可变的:
const 的意思是“这个名字永远指向这个对象”,而不是“这个对象永远不会改变”。如果你需要冻结对象本身,可以用 Object.freeze(user);不过在实践中,大多数代码只是依赖一个约定:const 对象不应该被修改。
let:确实需要重新赋值时
let 和 const 在各方面都一样,唯一的区别是它可以重新赋值。把它用于计数器、累加器、循环变量,以及任何值确实会随时间变化的地方。
如果你写了 let,却从来没有对这个变量重新赋值,就把它改成 const。大多数项目中的代码检查工具都会提醒你。
块级作用域
let 和 const 都是块级作用域。块就是 { 和 } 之间的任何内容:if 的主体、for 的主体、函数,甚至是一个单独的 { ... } 代码块。在块内声明的变量在块外并不存在。
这正是你想要的。它让变量只在其所属的工作范围内有效,也让意外的命名冲突不可能发生。
var 不会这样做,这也是避免使用它的主要原因。
var:函数作用域与意外
var 的作用域是最近的函数,而不是最近的块。这意味着在 if 或 for 中声明的 var 会泄漏到外层函数中:
这种松散的作用域是一长串经典 JavaScript bug 的根源,最有名的就是循环中每次迭代都共用同一个 var i,结果所有回调看到的都是最终的值。
var 还允许你在同一作用域中重复声明同一个名字而不报错,这会掩盖拼写错误:
现代代码几乎只用 let 和 const。var 出现在遗留代码库、老旧的 Stack Overflow 回答,以及需要在非常老的环境中运行的脚本里。
提升与暂时性死区
三种声明都会被提升,也就是说引擎在运行块中的代码之前就已经知道它们,但在声明语句运行之前,它们的行为各不相同。
var 会被提升并初始化为 undefined。你可以在 var 那一行之前引用它,而不会报错:
let 和 const 会被提升,但不会被初始化。在声明之前访问它们会抛出异常。从进入作用域到运行声明语句之间的这段区域,叫作暂时性死区(TDZ):
暂时性死区是一个特性,而不是 bug。它把“声明前使用”从一个悄无声息的 undefined 变成一个明显的错误,能发现很多拼写和顺序上的错误。
for...of 中的 const 循环变量
一个小但常见的情况:for...of 循环在每次迭代时都会创建一个新的绑定,所以即使循环变量在迭代之间“变了”,你也可以用 const 声明它。
每次迭代都有自己的 name 绑定,并不存在一个被反复赋值的变量。而经典的 for (let i = 0; i < n; i++) 仍然需要 let,因为 i 是一个会被递增的绑定。
一条实用规则
按以下优先顺序选择声明方式:
- 默认用
const。如果值不会被重新赋值,就明确表达出来。 - 绑定确实需要改变时,用
let。 - 只有在代码库要求时才用
var。
坚持这样做,每个变量的意图都一目了然:能否重新赋值,作用域是这个块还是这个函数。
MAX_RETRIES 和 users 从不改变,所以用 const。successful 会增长,所以用 let。user 在每次迭代中都是一个新的绑定,所以用 const。从上往下读,不用运行代码,你就能分辨出哪些值会变、哪些不会。
下一步:原始类型
现在你已经会声明变量了,下一个问题是它们能保存哪些类型的值。JavaScript 有一小组原始类型:数字、字符串、布尔值以及其他几种,每种都有自己的特点。这就是下一页的内容。
常见问题
JavaScript 中 let、const 和 var 有什么区别?
const 和 let 是块级作用域,在 ES2015 中引入;var 是函数作用域,历史更久。const 绑定不能重新赋值,let 绑定可以。var 还会被提升并初始化为 undefined,而 let 和 const 虽然也会被提升,但在声明语句运行之前无法使用(即暂时性死区)。
JavaScript 中 const 会让值变成不可变的吗?
不会。const 只阻止对绑定重新赋值。如果值是对象或数组,你仍然可以修改它的内容:const user = {}; user.name = 'Ada' 完全可以运行。如果需要真正的不可变,就用 Object.freeze 或相关的库。
现代 JavaScript 中还应该使用 var 吗?
几乎不用。let 和 const 的作用域规则更清晰,能在解析阶段发现更多 bug。你只会在遗留代码库,以及偶尔需要在不支持 ES2015 的环境中运行的脚本里见到 var。