都 2021 年了,如果你的 JavaScript 代码中还有 var 的话,不妨尽快迁移到 let 或 const,这是 ES6 提供的全新的关键字,比 var 强的地方主要有以下两点:
- 避免变量提升(创建暂时性死区)
- 支持块级作用域(避免全局变量污染)
避免变量提升
变量提升是 JavaScript 程序员心中无法抹去的痛,例如:
console.log(a)
var a = 1
竟然会输出 undefined,上面的代码在几乎其他任何主流语言中都会报错,因为上学的时候,老师教过我们要「先声明后使用」,哪有变量在声明之前就能使用的说法?
是的,JavaScript 就是这么神奇,搞了一个「变量提升」的概念,也就是说上面的代码等价于:
var a
console.log(a)
a = 1
var 的这个特性会在不知不觉中给开发者带来难以察觉的问题,例如下面的代码:
var name = 'David'
function test() {
// ... 这里是大量业务代码
// 突然想访问全局变量 name
console.log(name)
// ... 又是大量业务代码
// 不小心又定义了一个临时变量也叫 name
var name = 'Lucy'
}
test()
中间的 name 是 undefined,你会百思不得其解,而改成 let 之后一切就都正常啦,因为你会得到一个错误:
ReferenceError: Cannot access 'name' before initialization
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
暂时性死区的本质就是:只要一进入当前作用域,所有变量就已经存在,但不能用,只有声明之后才能用。
支持块级作用域
ES5 中只有全局作用于和函数作用域,单纯大括号包裹的范围是不存在作用域的,例如:
{
let a = 1
var b = 2
}
console.log(a) // ReferenceError: a is not defined.
console.log(b) // 2
可以看到,b 直接暴露到全局,而 a 在花括号外面是访问不了的,尤其是在 for 循环当中,假如我们用 var 来声明变量,那么就会污染全局,而如果使用 let 则只对 for 循环内生效:
for (var j = 0; j < 10; j++) {
// ...
}
console.log(j) // 10
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i) // ReferenceError: i is not defined
当用 let 声明循环变量的时候,你是不是认为 i 的作用域就是花括号包裹的循环体?其实并不是的,因为 i 并没有在循环体内定义,其实 i 的作用域就是设置循环变量的那部分,即 for 语句,循环体内部其实是一个单独的子作用域,不信你看:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码并未报错,而是输出了 3 次 abc,这表明函数内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域(因为同一个作用域不可使用 let 重复声明同一个变量)。当 for 循环中有异步操作的时候,用 var 和 let 的区别十分明显,例如:
for (var j = 0; j < 10; j++) {
setTimeout(() => console.log(j), 100) // 打印 10 个 10
}
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 100) // 打印 0 到 9
}