深入JavaScript- 作用域&作用域链

111 阅读4分钟

一、作用域(Scope)

1. 作用域是什么

作用域是当前的执行上下文,值和表达式在其中“可见”或可被访问。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。

2. 作用域分类

作用域有两种工作模型:动态作用域、静态作用域(又称词法作用域),JavaScript采用的是静态作用域。

JavaScript中可以分为三种作用域,分别是全局作用域函数作用域块级作用域(ES6新增)

  • 全局作用域:是指在整个代码中定义的变量和函数,它们可以在整个代码中访问。

    • 定义在最外层的变量或函数,或者说全局上下文中存储的变量或函数拥有全局作用域
    • 所有未声明的变量自动声明为拥有全局作用域的变量
  • 函数作用域:函数作用域也被称为局部作用域,每个函数都有自己的函数作用域,在函数内部定义的变量和函数只能在该函数内部访问。当函数执行完毕时,这些变量和函数的作用域将被销毁。使用var关键字声明的变量具有函数作用域。

  • 块级作用域:指代码中使用{ }包裹起来的区域,如ifforwhile、匿名块等,使用letconst关键字生命的变量具有块级作用域。

 var a = 1;
 function fn() {
   b = 2;
   var c = 3;
   return function(){
      console.log(a);  // 1
      console.log(b);  // 2
      console.log(c);  // 3
     }
   }
   
  {
    var d = 4
    const e = 5
   }

   var func = fn();
   func();
   console.log(a);   // 1
   console.log(b);   // 2
   console.log(c);   // c is not defined
   console.log(d);   // 4
   console.log(e);   // e is not defined
  • 全局作用域:变量abd,函数fnfunc
  • 函数作用域:变量c (函数外部访问不到,所以console.log(c)报错c is not defined
  • 块级作用域:变量e

3. 暂时性死区

暂时性死区的本质:只要进入当前作用域,使用letconst声明的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

另外只要作用域内存在let命令,它所声明的变量就“绑定”这个块级区域,不再受外部的影响。

ES6明确规定,如果区块中存在letconst命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域,只要在声明之前使用这些变量,就会报错。

在代码块内,使用let命令声明变量之前,该变量都是不可用的,这在语法上成为“暂时性死区”(temporal dead zone,简称TDZ)

var a = 1
if(a){
   a = 2   // ReferenceError
   let a
}

if的块级作用域中,变量a使用let命令声明,所以在声明之前都属于a的“死区”。a = 2 这行代码则会报错Uncaught ReferenceError: Cannot access 'a' before initialization

二、作用域链

1. 作用域链是什么

创建执行上下文的同时也会创建作用域链,它是由当前上下文和所有父级上下文的变量对象共同形成的链表。

根据查找规则,JavaScript 执行查找变量会从作用域链顶端开始依次查找对应变量**,** 如果找不到就会沿着作用域链一层层向外查找。假定在一个函数内访问一个变量,如果在当前函数内没有找到该变量会去父级作用域查找,父级作用域没有再去父级作用域的父级作用域,一直找到全局作用域,如果依旧没有找到才会抛出x is undefind 错误。

bar函数体中使用了变量ab,但在bar函数作用域中并没有找到这些变量,于是它就会到其父级foo函数作用域中继续查找,找到了变量b,变量a还没有找到。继续查找,到foo函数作用域的父级作用域中查找,找到变量a,至此查找结束。

  1. 当代码开始执行时就会创建全局执行上下文环境,此时在作用域中的全局(Global)中能找到全局对象window,在脚本(Script)中能还看到变量afunc,但是二者是用let声明的,也就是产生了上面提到的暂时性死区,此时若是获取和使用afunc则会报错。

  2. 代码在执行完foo函数后,可以作用域中的本地(Local)中看到foo函数作用域内有变量b和函数bar

  3. bar函数中需要用到变量aba是最外层变量,foo函数产生了闭包,所以foo函数里面的bar函数可以取到函数外层的b和模块最外层变量a,要注意而这里a并不是全局变量,是当前模块文件的最外层变量,和全局变量不同,它只能被模块文件内的代码访问。