一、何为作用域

JS 中的作用域,可以理解为一种标识符访问权限的规则。通过这个规则,可以确定变量在当前执行环境内,是否可以访问/操作。

二、作用域的产生

那作用域是如何产生的呢? 具体有什么用处呢?

从写的代码,到编译为电脑可以识别的指令。语言 => 机器指令,大致经历了以下阶段:

  1. 对源代码进行语法分析,得到词法单元。举例:var a = 1; 分析后,变为 vara=1;
  2. 对上一步产生的词法单元,再进行语法分析处理,构建抽象语法树(AST),类似于伪代码
  3. 根据 AST 生成,电脑可以执行的机器指令

这个过程中,词法分析又引擎负责处理。而语法分析、代码生成则由编译器处理。在语法分析阶段,作用域 则提供了所有标识符的访问、赋值权限。

作用域在产生在代码书写的位置。变量的操作权限由当前位置开始,逐级往上,直至找到第一个匹配规则。
A003-作用域 - 图1

三、作用域的查找

正如上面所提到的,语法分析时,要对代码中的变量,进行读写。那么就涉及到了赋值 、引用。于是,产生了 2 种查找方式。即:

  • LHS 查询:找到赋值操作的目标(可以理解为赋值)
  • RHS 查询:找到赋值操作的源头(可以理解为引用)

举例说明:

  1. var a = 1;
  2. console.log(a);
  1. 找到变量 a,当前作用域不存在变量,于是马上创建一个 a
  2. 给变量 a赋值,也就是进行 LHS查询
  3. 找到 console 对象和 log方式
  4. 进行一次 RHS查询,找到 变量 a
  5. 执行语句

四、作用域的修改

作用域的产生,是发生在语法分析阶段。代码编译好之后,还是有可能修改干预修改作用域的。

  • eval(修改词法作用域)

    1. function foo(str, a) {
    2. eval( str ); // 修改了当前作用域
    3. console.log( a, b );
    4. }
    5. var b = 2;
    6. foo( "var b = 3;", 1 ); // 1, 3
  • with(创建全新作用域) ```javascript // 重复引用同一个对象的多个属性 function f(obj) { with (obj) { a = 1; } } var o1 = { a: 0 }; var o2 = { b: 2 };

f(o1); console.log(o1.a); // 1

f(o2); console.log(o2.a); console.log(a); // a 被泄露在了全局作用域 ```

五、作用域的提升

在执行代码之前,处理变量/函数声明。

  • 变量提升:提升到当前词法作用域的最前端
  • 函数提升:只提升函数声明,不提升函数表达式

重复声明时,函数提升优先。

六、作用的类型

  • 全局作用域在任何位置,都可以访问到变量。常见的全局作用域:
    • 程序最外层定义的函数或变量
    • 所有未定义,直接赋值的变量
    • window 对象的属性、方法
  • 块级作用域:letconst 声明的变量,形成的作用域
  • 函数作用域:具名函数、匿名函数、立即执行函数表达式(IIFE)