在ES6之前,我们声明一个变量是这样做的:var a = 1;我们都知道 var 会有很多问题,比如:可重复声明,变量提升,不能声明常量(约定常量名大写,但是还是可以重新赋值),没有块级作用域等。为了解决这些问题,ES6新增了 let 和 const 用来声明变量和常量。这可比 var 好用多了,不用担心之前的那些问题了。这篇文章就聊聊我们为什么应该使用 let 和 const,而不是 var 。
全局变量
使用 var 声明的变量会在全局对象上增加一个属性,而 let 和 const 不会。废话少说,直接上代码瞧瞧:
var a = 1
window.a // 1
this.a // 1
let b = 2
window.b // undefined
this.b // undefined
const MAX = 10
window.MAX // undefined
this.MAX // undefined
我们可以看到,使用 var 声明确实会在全局对象(浏览器环境为 window )上添加属性。
重复声明
使用 var 声明的变量是可以重复声明的,而 let 和 const 会报错:
var a = 1
var a = 2
a // 2
let b = 1
let b = 2 // SyntaxError: Identifier 'b' has already been declared
const MAX = 10
const MAX = 11 // SyntaxError: Identifier 'MAX' has already been declared
我们可以看到,使用 var 重复声明不会报错,而是赋予该变量新的值,这样做很危险的,我们如果不小心将之前声明的变量覆盖了,是不会提醒错误的。而 let 和 const 为我们解决了这个问题,如果你重复声明了,会直接给你报 语法错误:标识符早已被声明 。let 和 const
声明的变量不能重复声明,即使用 var 也不行:
let b = 1
var b = 1 // SyntaxError: Identifier 'b' has already been declared
const MAX = 10
var MAX = 11 // SyntaxError: Identifier 'MAX' has already been declared
块级作用域
在ES6之前,是没有块级作用域的:
{
var a = 1
}
a // 1
{} 是一个代码块,但是这里的结果和我们正常的思维有些不符合,因为这里没有块级作用域。我们再看看 let :
{
let b = 1
}
b // ReferenceError: b is not defined
我们看到,这里会报引用错误,b 未定义。说明这里是存在块级作用域的,要访问 let 声明的变量 b ,只能在该块级作用域中访问。 我们看下面一段代码:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
after 1s
// 5 5 5 5 5
上面代码的结果会在1s后输出5个5,有的小伙伴就疑问🤔️了,这是为什么呢?其实上面代码相当于:
var i = undefined
for (i = 0; i < 5; i++) {
...
}
这里就是 var 其实是全局变量,是块级作用域的。所以结果会输出为5。我们可以使用闭包来解决:
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, 1000)
})(i)
}
after 1s
// 0 1 2 3 4
这里利用了闭包的特性,可以访问创建时所处上下文环境。不过闭包的方式不够优雅,在ES6中我们可以直接使用 let 来解决:
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
after 1s
// 0 1 2 3 4
比起闭包的方式,优雅多了。
变量提升
使用 var 定义的变量,我们可以在声明语句之前就可以访问它,值为 undefined :
console.log(a) // undefined
var a = 1
a // 1
其实在上面讲解块级作用域的时候就已经讲到了,var 会提升到 全局作用域 的顶部或者 函数作用域 的顶部,是有一个变量提升的行为的。而 let 声明的变量在声明之前是访问不到的:
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 1
b // 1
我们可以看到,在声明之前访问变量 b 会报错,但是这里并不是说使用 let 声明不会使得变量提升,而是不能访问。这里有个 暂时性死区 的概念。
暂时性死区
从上面变量提升的例子可以看到,使用 let 声明的变量在初始化之前是不能访问的,这里的 暂时性死区 指的就是从块顶部到初始化变量的一个区间,在此之间访问变量都会报错。
常量
我们可以使用 const 声明常量:
const MAX = 10
MAX = 11 // TypeError: Assignment to constant variable
const PERSON_INFO = {
name: '张三',
age: 18
}
PERSON_INFO.age = 20
PERSON_INFO // {name: "张三", age: 20}
PERSON_INFO = {} // TypeError: Assignment to constant variable
常量是不允许修改的,所以对其重新赋值会报错。当常量为对象时,因为其是对一个地址的引用,当改变原对象的值时是没有问题的,因为地址没有变,当重新赋值给其他对象,就改变了地址引用,所以报错。
总结
这些都是比较基础的知识,本来是想结合ECMAScript规范来聊的,但是有一些关于规范的名词和概念,讲起来也要一些篇幅;所以暂时没有聊到,后面再聊。总之,我们现在全面拥抱ES6,使用 let 和 const 来声明变量和常量。