1. let 命令

1.1 基本用法

ES6 新增了 let 命令,用来声明变量。它的用法类似于 var 但是所声明的变量,只在 let 命令所在的代码块内有效。

  1. {
  2. let a = 10;
  3. var b = 1;
  4. }
  5. a // ReferenceError: a is not defined
  6. b // 1

let 声明的变量报错,var没有,这表明,let 声明的变量只在它所在的代码块有效。
for 循环的计数器,适合用 let 命令

  1. for( let i=0; i<10;i++){
  2. // ...
  3. }
  4. console.log(i) // ReferenceError: a is not defined

i 只在 for 内有效,循环体外引用就报错
下面的代码如果是用 var,最后输出的是 10

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

上面代码中,变量 ivar 命令声明的。在全局内有效,所以全局内只有一个变量 i
每一次循环,变量 i 的值都会发生改变,而循环内被赋给数组 a 的函数内部的 console.log(i)里的i指向全局的i。也就是说,所有数组 a 成员里面的i,指向的都是同一个 i 导致运行时输出的是最后一轮的 i 值(10)。
如果使用 let 声明的变量仅在块级作用域内有效,最后输出的是6.

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

上面代码中,变量 i 是let 声明的,当前的 i 只在本轮循环有效,每次循环 i 都是一个新的变量,所以最后输出的是 6.你可能会问,如果每一轮循环的变量 i 都是重新发明的,那它怎么知道上一轮循环的值,从而计算出本循环的值?这时因为 JavaScript 引擎内部记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。
另外,for 循环还有一个特别之处。就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

  1. for(let i=0;i<3;i++){
  2. let i = 'abc'
  3. console.log(i)
  4. }
  5. // abc
  6. // abc
  7. // abc

正确运行,输出3次abc。表明函数内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域

1.2 不存在变量提升

var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
let 命令改变了语法行为,它所生命的变量一定要在声明后使用。否则报错

  1. // var
  2. console.log(foo) // undefined
  3. var foo = 2;
  4. // let
  5. console.log(bar) // ReferenceError
  6. let bar = 2;

1.3 暂时性死区

只要块级作用域内存在 let 命令,它所生命的变量就绑定这个区域。

  1. var tmp = 123;
  2. if(true){
  3. tmp = 'abc' // ReferenceError
  4. let tmp;
  5. }

存在全局变量tmp,但是块级作用域内let 又声明了一个局部变量tmp。导致后者绑定了这个块级作用域,所有在let声明变量前,对tmp赋值会报错。
ES6 明确规定。如果区块内存在 let const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内 使用 let 命令声明变量之前,该变量都是不可用的。这在语法上称为“暂时性死区”(temporal dead zone,简称 TDZ)

  1. if(true){
  2. // TDZ 开始
  3. tmp = 'abc' //ReferemceErrpr
  4. let tmp; // TDZ 结束
  5. console.log(tmp) // undefined
  6. tmp = 123
  7. console.log(tmp) // 123
  8. }
  9. // let 命令声明变量 tmp 之前 都属于 变量 tmp 的死区

“暂时性死区”,也意味着 typeof 不再是一个百分之百安全的操作。

  1. typeof x // ReferenceErrpr
  2. let x;

变量 x 使用 let 命令声明,所以在声明之前,都属于 x 的死区,只要用到该变量就会报错。
因此 typeof 运行时就会抛出一个 ReferenceError
**
作为比较 如果一个变量根本没有被声明,使用typeof 反而不会报错。

  1. typeof undeclared_variable // undefined

有些死区比较隐蔽,不太容易发现

  1. function bar( x=y,y=2){
  2. return [x,y];
  3. }
  4. bar() // 报错
  5. // x = y ,因为 y 还没有声明,属于“死区”,如果y的默认值是就不会报错/

下面的代码也会报错, 与 var 的行为不同。

  1. var x = x // 不报错
  2. let x = x // ReferemceErrpr: x is not defiend

因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错,上面这行就属于这个情况,在变量 x 的声明语句还没有执行完成前就去取 x 的值,导致报错 “x ” 未定义

ES6 规定暂时性死区和 letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外i的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。 总之,暂时性死区的本质就是,只要已进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的哪一行代码出现,才可以获取和使用该辨明

1.4 不允许重复声明

let 不允许在相同作用域内,重复声明同一个变量

  1. // 报错
  2. function func(){
  3. let a = 1;
  4. var a = 1;
  5. }
  6. //
  7. function func(){
  8. let a = 1;
  9. let a = 1;
  10. }

因此,不能在函数内部重新声明参数。

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

2. 块级作用域

2.1 为什么需要块级作用域?

2.2 ES6的块级作用域

2.3 块级作用域与函数声明

3. const 命令

3.1 基本用法

3.2 本质

3.3 ES6 声明变量的六种方法

4. 顶层对象的属性

5. globalThis对象