ES6 块级作用域

365 阅读3分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

let 与 const

ES5 中声明变量命令只有两种 var 和 function

ES6 中除了 var、function,新增了 let、const、class、import,一共六种

  1. let 是 ES6 新增的声明命令,功能类似于 ES5 中的 var 关键字

  2. let 声明的变量尽在所在代码块内有效

    {
        let a = 0
        var b = 1
    }
    console.log(b) // 1
    console.log(a) // ReferenceError
    // 这表明 let 声明的变量只在它所在的代码块有效
    

    let 命令很适合在 for 循环中使用

    var a = [];
    for (var i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 10
    // var 声明的 i,全局有效,每一次循环,新的 i 值都会覆盖旧值,导致最后输出的是最后一轮的 i 的值
    
    var b = [];
    for (let j = 0; j < 10; j++) {
      b[j] = function () {
        console.log(j);
      };
    }
    b[6](); // 6
    // 变量 j 使用 let 声明,当前的 j 只在本轮循环有效,每一次循环的 j 其实都是一个新的变量,所以最后输出的是 6
    
  3. let 命令不允许重复声明

    // SyntaxError
    function test1() {
      let a = 10
      var a = 1
    }
    // SyntaxErrot
    function test2() {
      let a = 10
      let a = 1
    }
    // SyntaxError
    function test3(a) {
        let a = 1
    }
    
  4. 暂行性死区

    // ReferenceError
    console.log(a)
    let a = 0
    

    同一代码块中,使用 let 命令声明的变量,不能在声明之前使用,会报引用错误

    代码块中,变量声明前,该变量不可用,语法上称之为暂行性死区(temporal dead zone,简称 TDZ)

  5. const 和 let 作用和特性类似,只在当前代码块有效,不能重复声明

    const 声明时必须赋初值,且不可改变其值

    const a = 1
    a = 2 // TypeError
    const b // SyntaxError
    
  6. let、const 变量提升

    下面代码 let 重复声明会报语法错误,但在报错之前 console.log 不能正常输出

    说明存在变量提升

    let a = 1
    console.log(a)
    let a = 2 // SyntaxError
    

    但是由于暂行性死区的存在,我们无法在声明之前使用变量

    console.log(a) // undefined
    console.log(b) // ReferenceError
    
    var a = 2
    let b = 2
    

    注:变量的定义分为创建 -> 初始化为 undefined -> 赋值三个阶段

    let 的创建过程被提升,但是初始化没有提升

    var 的创建和初始化被提升

    function 的创建、初始化和赋值都被提升

块级作用域

  1. ES5 中只有全局作用域和函数作用域

    // 全局作用域
    var a = 0
    
    function test() {
        // 函数作用域
        var a = 1
    }
    

    存在的问题

    var a = 0
    function test() {
        console.log(a)
        if(false) {
            var a = 1
        }
    }
    test() // undefined
    
    

    函数作用域下中 if 中 var a 命令变量提升,导致 a 为 undefined

  2. ES6 中规定 {} 内部为一个独立的块级作用域

    外层代码块不受内层代码块的影响

    外层作用域无法读取内层作用域的变量

    内层作用域可以定义外层作用域的同名变量

    {{
      let a = 0
      {
          let a = 1
          {
              console.log(a) // 报错
          }
      }
    }}
    
  3. 块级作用域的出现让广泛运用的的立即执行函数不再必要

    // IIFE 写法
    (function () {
      var tmp = ...;
      ...
    }());
    
    // 块级作用域写法
    {
      let tmp = ...;
      ...
    }
    

函数声明

  1. ES5 规定函数只能在全局作用域和函数作用域中声明

    // 情况一
    if (true) {
      function fn() {}
    }
    
    // 情况二
    try {
      function fn() {}
    } catch(e) {
    }
    

    以上为非法的声明,但是为了兼容旧代码,浏览器没有遵守此规定,可以运行,不会报错,但是严格模式下会报错

    // ES5 严格模式
    'use strict';
    if (true) {
      function fn() {}
    }
    // 报错
    
  2. ES6 引入块级作用域,明确允许可以在块级作用域声明函数

    // ES6 严格模式
    'use strict';
    if (true) {
      function fn() {}
    }
    // 不报错
    
  3. ES6 规定块级作用域中的函数声明,无法在作用域外引用

    function fn() { 
        console.log('outside') 
    }
    (function () {
      if (false) {
        // 重复声明一次函数
        function fn() { 
            console.log('inside')
        }
      }
      fn()
    }())
    

    在 ES5 中会打印出 inside,因为 if 块里面的 fn 被提升到自执行函数头部

    在 ES6 中则打印出 outside,因为在 if 块内声明的 fn,作用域外无法访问,只能执行外部声明的 fn

    由于行为差异较大,ES6 规定浏览器可以不遵守该项,有自己的行为方式

    1. 允许在块级作用域内声明函数
    2. 函数声明类似于 var,即提升到全局作用域或函数作用域的头部
    3. 同时,函数声明会提升到所在的块级作用域的头部

    上面示例代码在 chrome 环境中可能会报错,因为实际运行的代码如下

    function fn() { 
        console.log('outside') 
    }
    (function () {
      var fn = undefined
      if (false) {
        function fn() { 
            console.log('inside')
        }
      }
      fn()
    }())
    // Uncaught TypeError: fn is not a function