JS作用域和作用域链

184 阅读5分钟

作用域 (Scope)

1.什么是作用域

  • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的
  • 简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期
function Fun() {
    var inVariable = "内部变量";
}
Fun();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
//inVariable是在Fun函数内部被定义的,属于局部变量,在外部无法访问,于是会报错

从存储上来解释的话,作用域本质上是一个对象,  作用域中的变量可以理解为是该对象的成员

总结:作用域就是代码的执行环境,全局作用域就是全局执行环境,局部作用域就是函数的执行环境,它们都是栈内存

2.作用域分类

(1)全局作用域

  • 直接写在script标签的JS代码,都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量)。
  • 全局变量在全局的任何位置下都可以使用;全局作用域中无法访问到局部作用域的中的变量。
  • 全局作用域在页面打开的时候创建,在页面关闭时销毁。
    let a = 10 
    function fn () {
      console.log(a)
     }
    fn() //  10 
  • 所有 window 对象的属性拥有全局作用域

    var和function命令声明的全局变量和函数是window对象的属性和方法

    let命令、const命令、class命令声明的全局变量,不属于window对象的属性

这里函数foo()内部并没有声明name变量,但是依然打印了name的值,说明函数内部可以访问到全局作用域,读取name变量。再来一个例子:

hobby='music';
function foo(){
    hobby='book';//注释本行,输出结果为music
    console.log(hobby);
}
foo();//book

这里全局作用域和函数foo()内部都没有声明hobby这个变量,为什么不会报错呢?这是因为hobby='music';写在了全局作用域,就算没有var,let,const的声明,也会被挂在window对象上,所以函数foo()不仅可以读取,还可以修改值。也就是说hobby='music';等价于window.hobby='music';

(2)函数体作用域(局部作用域)

函数体的作用域是通过隐藏内部实现的。换句话说,就是我们常说的,内层作用域可以访问外层作用域,但是外层作用域不能访问内层。原因,说到作用域链的时候就迎刃而解了。

function foo(){
    var age=19;
    console.log(age);
}
console.log(age);//ReferenceError:age is not defined

很明显,全局作用域下并没有age变量,但是函数foo()内部有,但是外部访问不到,自然而然就会报错了,而函数foo()没有调用,也就不会执行。

      var a = 10
      function fn () {
        var b = 20
        function fn2 () {
          console.log(a + b)
        }
        fn2()
      }
      fn() //30
      console.log(b) //b is not defined
  • 在函数作用域中可以访问全局变量,在函数的外面无法访问函数内的变量。
  • 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有就向上一作用域中寻找,直到找到全局作用域,如果全局作用域中仍然没有找到,则会报错。

(3)块级作用域

  • ES6之前JavaScript采用的是函数作用域+词法作用域,ES6引入了块级作用域。
  • 任何一对花括号{}中的语句集都属于一个块,在块中使用let和const声明的变量,外部是访问不到的,这种作用域的规则就叫块级作用域。
  • 通过var声明的变量或者非严格模式下创建的函数声明没有块级作用域。
  • 在 Web 浏览器中,全局作用域被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。
  • 在 Node环境中,全局作用域是 global 对象。
      function fn () {
        let a = 1
        {
          var b = 1 // 未用let或const,不形成b的块作用域,依然是fn的函数作用域
          let c = 1 // c形成块作用域,只可在当前代码块中访问
        }
        console.log(a) //1
        console.log(b) //1
        console.log(c) // c is not defined
      }
      fn()

作用域链

  • 当代码在一个环境中创建时,会创建变量对象的一个作用域链(scope chain)来保证对执行环境有权访问的变量和函数。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。如果是函数执行阶段,那么将其activation object(AO)作为作用域链第一个对象,第二个对象是上级函数的执行上下文AO,下一个对象依次类推。
  • 当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。