聊一聊ES6:let 和 const

509 阅读4分钟

在ES6之前,我们声明一个变量是这样做的:var a = 1;我们都知道 var 会有很多问题,比如:可重复声明,变量提升,不能声明常量(约定常量名大写,但是还是可以重新赋值),没有块级作用域等。为了解决这些问题,ES6新增了 letconst 用来声明变量和常量。这可比 var 好用多了,不用担心之前的那些问题了。这篇文章就聊聊我们为什么应该使用 letconst,而不是 var

全局变量

使用 var 声明的变量会在全局对象上增加一个属性,而 letconst 不会。废话少说,直接上代码瞧瞧:

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 声明的变量是可以重复声明的,而 letconst 会报错:

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 重复声明不会报错,而是赋予该变量新的值,这样做很危险的,我们如果不小心将之前声明的变量覆盖了,是不会提醒错误的。而 letconst 为我们解决了这个问题,如果你重复声明了,会直接给你报 语法错误:标识符早已被声明letconst 声明的变量不能重复声明,即使用 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,使用 letconst 来声明变量和常量。

Reference

developer.mozilla.org/zh-CN/docs/…