let命令

了解块级作用域

  • 块级作用域是一个语句,将多个操作封装在一起,通常是放在一个大括号里,没有返回值。(但是对象声明用到的大括号不包括块级作用域,它包括的是对象)
  • 块级作用域之中的每个函数都类似一个代码块。(但是这个代码块不包含它体内的另一个函数内的内容,类似于“我的附庸的附庸不是我的附庸”,函数1所包含的函数2的内容也不属于函数1的代码块)

块级作用域于函数声明

  • ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let ,在块级作用域之外不可引用。
  • 但应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

但为了减轻因此产生的不兼容问题,在ES6环境的浏览器中:

  1. 允许在块级作用域内声明函数。
  2. 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  3. 同时,函数声明还会提升到所在的块级作用域的头部。
    ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理

ES6 允许块级作用域的任意嵌套

先了解一下,匿名立即执行函数表达式(匿名 IIFE)可以用块级作用域实现:

1
2
3
4
5
6
7
8
9
10
11
// IIFE 写法
(function () {
var a = 1;
console.log(a);
}());

// 块级作用域写法
{
let a = 1;
console.log(a);
}

下面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。。

1
2
3
4
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};

注意:内层作用域可以定义外层作用域的同名变量。


let 命令

  • 声明变量,用法类似于var,但是所声明的变量只在let命令所在的代码块内有效。
  • for循环的计数器,就很合适使用let命令。

for循环的例子(var与let)

  • 首先要明确,for循环有一个特别之处,设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
    1
    2
    3
    4
    5
    6
    7
    for (let i = 0; i < 3; i++) {
    let i = 'abc';
    console.log(i);
    }
    // abc
    // abc
    // abc
  • 上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

使用var的例子:

1
2
3
4
5
6
7
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10,数组a中的10个元素都是打印10

变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。所以a[0]指向的i以及a[1]到a[8]指向的i都被后来循环的a[9]指向的i(也就是10)覆盖掉了。而局部变量的话就是每循环一次产生一个新的i,所以不会被覆盖。

使用let的例子:

1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。


暂时性死区

  • ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量从一开始就形成了封闭作用域。凡是在声明之前使用这些变量(包括声明或者赋值),就会报错。
  • 存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
    1
    2
    3
    4
    5
    var tmp = 123;
    if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
    }

let不允许重复声明

不能在函数内部重新声明参数

不能在函数内部重新声明参数,这相当于在同一个块级作用域里面两次用let声明变量:

1
2
3
4
5
6
7
8
9
10
11
function func(arg) {
let arg;
}
func() // 报错

function func(arg) {
{
let arg;
}
}
func() // 不报错,注意这里是两个代码块了(不理解的话可以看下面的另一个例子)

下面的函数有两个代码块,都声明了变量n,运行后输出5。(因为代码块2的变量n不作用于代码块2之外)这表示外层代码块不受内层代码块的影响

如果两次都使用var定义变量n,最后输出的值才是 10。

1
2
3
4
5
6
7
8
9
function f1() {
let n = 5; //代码块1定义的变量n
if (true) {
let n = 10; //代码块2定义的变量n
//在这console.log(n);会得到10
}
console.log(n); // 5
}
f1();