ECMAScript系列之let

145 阅读2分钟

let 关键字

ES6 新增了 let 命令,用来声明变量。

let 特点

不属于顶层对象 window

使用 var 关键字声明的变量实际上是会挂在 window 对戏那个上面的,例如:

var a = 188

# 等同于

window.a = 188

而下面这段代码就不一样了,使用 let 声明的变量拥有块级作用域,不存在 window 上

let a = 188

console.log(windopw.a) // 报错

不允许重复声明

我们先来看段代码

var a = 1
var a = 6

上面代码使用 var 关键字声明了同名的 a 变量,在内存中表现相当于给 a 赋予了新的值对前者进行了覆盖

而使用 let 声明变量是不允许这种不规范操作的

let a = 168
let a = 168
<!-- Uncaught SyntaxError: Identifier 'a' has already been declared (at -->

不存在变量提升

片段 1

console.log(a)  // undefined
var a = 1

片段 2

console.log(a)  // 抛出错误
let a = 1

从程序角度来看,let 的形式抛出异常信息更加合理

暂时性死区

只要块级作用域内存在 let 命令,它所声明的变量就绑定在了这个区域,不再受外部的影响。

var a = 5
if (true) {
    a = 6
    let a
}
// Uncaught ReferenceError: Cannot access 'a' before initialization

上面代码中,存在全局变量 a ,但是块级作用域内 let 又声明了一个局部变量 a ,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 a 赋值会报错。

ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

有时“暂时性死区”比较隐蔽,比如:

function foo(b = a, a = 2) {
    console.log(a, b)
}
foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization

块级作用域

let 实际上为 JavaScript 新增了块级作用域

{
    let a = 5
}
console.log(a) // undefined

a 变量是在代码块 {} 中使用 let 定义的,它的作用域是这个代码块内部,外部无法访问。

我们再看一个项目中很常见的 for 循环:

for (var i = 0; i < 3; i++) {
    console.log('循环内:' + i) // 0、1、2
}
console.log('循环外:' + i) // 3

如果改为 let 会怎么样呢?

for (let i = 0; i < 3; i++) {
    console.log('循环内:' + i) // 0、1、2
}
console.log('循环外:' + i) // ReferenceError: i is not defined

继续看下面两个例子的对比,这时 a 的值又是多少呢?

if (false) {
    var a = 5
}
console.log(a) // undefined
if (false) {
    let a = 5
}
console.log(a)
// Uncaught ReferenceError: a is not defined

思考题

请说出下列输出结果

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i)
    })
}
// 3、3、3

但我们的期望是输出结果 0 1 2,怎么做到呢?

方案 1:闭包

for (var i = 0; i < 3; i++) {
        (function (j) {
          setTimeout(() => {
            console.log(j);
          });
        })(i);
      }

方案 2:使用 let

for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i)
    })
}

大家可以把上面这段代码粘贴到 babel 中:www.babeljs.cn/repl , 看下经过 babel 转化为的 ES5 代码是什么样子的。

大家会发现其实 babel 把这段代码转化成了闭包的形式:

"use strict"

var _loop = function _loop(i) {
    setTimeout(function() {
        console.log(i)
    })
}

for (var i = 0; i < 3; i++) {
    _loop(i)
}

相信通过这个思考题,大家对于 let 关键字的使用会有进一步的理解。

推荐阅读