09-块级作用域

112 阅读4分钟

块级作用域

作用域

作用域指在程序中定义变量的区域,该位置决定了变量的生命周期,也就是说,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期

ES6之前,有两种作用域:全局作用域和函数作用域

变量提升带来的问题

变量容易在不察觉的情况下被覆盖

 var myname = "极客时间"
 function showName(){
     console.log(myname);
     if(0){
         var myname = "极客邦"
         }
     console.log(myname);
 }
 showName()

这段代码的输出应该是undefined,我们来简单分析以下:

  • 首先,当刚执行到showName函数调用时,执行上下文和调用栈的状态如图:

    变量被覆盖例子.png

  • 执行上下文都创建完之后,开始执行showName函数内部的代码,这里要执行console.log(myname),所以要查找这个myname变量,JS会优先从当前的执行上下文中查找变量,由于变量提升,当前的执行上下文中就包含了变量myname,值为undefined,所以打印出来的值应该是undefined

本应销毁的变量没有被销毁

 function foo(){
     for (var i = 0; i < 7; i++) {
     }
     console.log(i); 
 }
 foo()

这里输出的是7

这同样也是又变量提升导致的,所以在创建执行上下文阶段,变量i就被提升了,当for循环执行结束后,变量i并不会被销毁

ES6是如何解决变量提升带来的缺陷

ES6引入了letconst关键字,让JS能像其他语言一样拥有了块级作用域

使用let关键字声明的变量是可以被改变的,而使用const声明的变量其值是不可以被改变的。但不管怎样,两者都可以生成块级作用域

 function varTest() {
     var x = 1;
     if (true) {
         var x = 2;  // 同样的变量!
         console.log(x);  // 2
     }
     console.log(x);  // 2
 }

在这段代码中,有两个地方都定义了变量x,第一个地方在函数块的顶部,第二个地方在if块的内部,由于var的作用范围是整个函数,所以在编译阶段,会生成如下的执行上下文

var的缺陷.png

所以最后输出的时候应该是2,而对于相同逻辑的代码,其他语言输出的应该是1,因为if块中的声明不应该影响到块外的变量

现在用let对这段代码重新改造一下:

 function letTest() {
     let x = 1;
     if (true) {
         let x = 2;  // 不同的变量
         console.log(x);  // 2
     }
     console.log(x);  // 1
 }

这样最后一行代码就输出1了,因为let关键字支持块级作用域,所以不会把if块中通过let声明的变量放到变量环境中

所以现在,作用块内声明的变量不影响块外面的变量

JS是如何支持块级作用域的

要解决这个问题,我们需要站在执行上下文的角度来揭开答案

 function foo(){
     var a = 1
     let b = 2
     {
         let b = 3
         var c = 4
         let d = 5
         console.log(a)
         console.log(b)
     }
     console.log(b) 
     console.log(c)
     console.log(d)
 }   
 foo()

我们来分析一下上述代码的执行流程:

  • 首先,先编译并创建执行上下文,如图:

    JS支持块作用域例子1.webp 可以得出以下结论:

    1. 函数内部通过var声明的变量,会被存放到变量环境中

    2. 通过let声明的变量,会被存放到词法环境中

    3. 函数作用域中的内部作用域,通过let声明的变量没有存放到词法环境

    4. 然后执行代码,执行到代码块时,变量环境中的a和词法环境中的b已经被赋值

    JS支持块作用域例子2.png

  • 进入代码块后,作用域块中通过let声明的变量,会被存放在词法环境的一个单独的区域中,这个区域中的变量并不影响作用域块外面的变量

  • 其实,在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶,作用域执行完后,该作用域的信息就会从栈顶弹出,这是词法环境的结构,此处的变量是let或者const声明的

  • 现在执行到console.log(a)这行代码,需要在词法环境和变量环境中查找变量a的值,具体查找方式:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给JavaScript引擎,如果没有查找到,那么继续在变量环境中查找,如图所示:

    变量的查找.png

  • 作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,所以现在的上下文环境为:

JS支持块作用域例子3.webp