【JS核心概念】作用域与作用域链

重点:


一、作用域

简单来说,作用域就是变量和函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。作用域又分为全局作用域、局部作用域以及ES6中的块级作用域。

  • 全局作用域
    • 全局作用域中的变量和函数在任何地方都可以被访问到;
    • 未定义直接赋值的变量为自动变为全局变量;
    var a1 = 1;
    function say1() {
        console.log(a1); // 1,函数内部也可以访问全局变量
        a2 = 2;
        console.log(a2); // 2,a2自动声明为拥有全局作用域,在严格模式下这种写法会报错
    }
    say1();
    console.log(a2); // 2
    
    // 结果为:1 2 2
  • 局部作用域
    • 在函数中声明的变量会变为函数的局部变量,只能在函数内部使用,因此函数作用域又称为局部作用域;
    • 每个函数都有自己的作用域;
    function say2(a) {
        console.log(a); // 2
        var b = 1;
        console.log(b); // 1
    }
    say2(2);
    console.log(a); // ReferenceError: a is not defined,函数的参数也是该函数的局部变量
    console.log(b); // ReferenceError: b is not defined,局部变量不能被外部访问
  • 块级作用域
    • ES5中没有块级作用域,这导致很多场景都不合理:

      • 内层变量可能会覆盖外层变量
          var a = 1;
          // 函数fn的作用是if代码块内部使用内层的变量a,外部使用外层的变量a。
          // 但是函数fn执行后,输出结果为undefined,原因在于变量提升导致内层的变量a覆盖了外层的变量a。
          function fn() {
              console.log(a);
              if (false) {
                  var a = 2;
              }
          }
          fn(); // undefined
      
      • 用来计数的循环变量泄漏为全局变量
          // 变量i用于控制循环,但是循环结束后,变量i没有被销毁,而是变成了全局变量
          for (var i = 0; i < 5; i++) {
              console.log(i); // 0 1 2 3 4
          }
          console.log(i); // 5
      
    • ES6新增了块级作用域

      • 使用let和const命令声明变量会形成块级作用域
          function fn() {
              let a = 1;
              if (true) {
                  let a = 2;
              }
              console.log(a);
          }
          fn(); // 1
      
          for (let i = 0; i < 5; i++) {
              console.log(i); // 0 1 2 3 4
          }
          console.log(i); // ReferenceError: i is not defined
      
          {
              const c = 'C'; 
              {
                  console.log(c); // C
              }
          }
          console.log(c); // ReferenceError: c is not defined
      

二、作用域链

作用域链,顾名思义,可以想象为是由许多个作用域串在一起形成的一个链。那么问题来了,这许多个作用域是从何而来呢?又是如何串在一起形成链的呢?

  • 作用域链的形成
    • 程序执行时,会先创建全局上下文,并将全局上下文的变量对象挂载在作用域链的后端端;
    • 如果全局上下文中调用了一个函数,则创建该函数的执行上下文,并将该函数上下文的变量对象挂载在全局上下文变量对象之上;
    • 如果该函数的内部又调用了一个函数,同样创建该函数的执行上下文,并将其变量对象挂载在外层函数上下文变量对象之上……
    • 以此类推,形成了一个作用域链。
  • 作用域链的特点
    • 作用域链的前端是当前正在执行的代码所处环境的变量对象;
    • 全局上下文的变量对象位于作用域链的后端
  • 查找标识符

当在某个环境中为了读取或者写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。

搜索过程从作用域链的前端开始,向后逐级查询与给定名字匹配的标识符,如果找到了,则停止搜索,变量就绪;如果追溯到了全局环境的变量对象还是没有找到,则说明该变量尚未声明。

三、练习时刻

    var a = 1;
    function fn() {
        console.log('1:' + a);
        function bar() {
            console.log('2:' + a)
        }
        var a = 2;
        bar();
        console.log('3:' + a);
    }
    
    fn();
    
    // 输出结果为:
    // 1:undefined => 使用var定义的变量会在当前作用域提升,且只提升声明,不提升赋值
    // 2:2 => 函数bar定义在函数fn中,因此其作用域为bar->fn->global,bar中没有定义a,则去fn中查找,在fn中找到后返回
    // 3:2 => 这行代码所在的作用域为fn->global,所以会在先在fn中查找a,找到后返回a的值
    var a = 1;
    function fn() {
        console.log('1:' + a);
        var a = 2;
        bar();
        console.log('2:' + a);
    }
    function bar() {
        console.log('3:' + a);
    }
    
    fn();
    
    // 输出结果为:
    // 1:undefined => 使用var定义的变量会在当前作用域提升,且只提升声明,不提升赋值
    // 3:1 => 函数bar是定义在全局作用域中的,所以作用域链是bar->global,bar中没有定义a,则去global中查找
    // 2:2 => 这行代码所在的作用域为fn->global,所以会在先在fn中查找a,找到后返回a的值
    var a = 1;
    function fn() {
        console.log('1:' + a);
        a = 2;
    }
    a = 3;
    function bar() {
        console.log('2:' + a);
    }
    
    fn();
    bar();
    
    // 输出结果为:
    // 1:3 => ① fn中的a=2是给变量a赋值,而不是声明变量a,因此执行fn时,查找到的变量a是全局作用域中的a;② JS中的代码是顺序执行的,因此执行fn之前已经执行了a=3,此时全局作用域中的a的值为3
    // 2:2 => ① fn中的a=2修改了全局作用域中a的值,因此执行bar时,a的值为2
    var a = 1;
    function fn(f) {
        var a = 2;
        return f;
    }
    function bar() {
        console.log(a)
    }
    
    var f1 = fn(bar);
    f1();
    
    // 输出结果为:
    // 1 => 函数中变量的值由函数定义时的所在的作用域链决定
    var a = 1;
    function fn(f) {
      var a = 2;
      return function () {
        console.log(a)
      }
    }
    
    var f1 = fn();
    f1();
    
    // 输出结果为:
    // 2 => 函数中变量的值由函数定义时的所在的作用域链决定



posted @ 2020-01-16 15:21  嘉平十五  阅读(273)  评论(0编辑  收藏  举报