Scope & Closures

Posted by Zxd on February 28, 2019

1.作用域是什么

1.1 编译原理

在传统编译语言的流程中,程序中的一段代码会在执行前经历三个步骤,统称为编译

  • 分词/词法分析(Tokenizing/Lexing)
    • 将字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元(token)
1
2
var a = 2;
// 分解成 var, a, =, 2, ; 五个词法单元
  • 解析/语法分析(Parsing)
    • 将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树. 抽象语法树(Abstract Syntax Tree, AST)
  • 代码生成
    • 将 AST 转换为可执行代码的过程称为代码生成.
1
将var a = 2; 的AST转换为一组机器指令, 用来创建一个叫作a的变量(包括分配内存等),并将一个值2储存在a中

但是 JavaScript 引擎要复杂得多,其在语法分析和代码生成阶段有特定的步骤对运行性能进行优化. JavaScript 的编译过程不是发生在构建之前. 大部分情况下编译发生在代码执行的前几微秒的时间内.

任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)

1.2 理解作用域

  • 引擎
    负责整个 JS 程序的编译及执行过程
  • 编译器
    负责语法分析和代码生成等
  • 作用域
    负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施严格规则,确定当前执行的代码对这些标识符的访问权限

变量的赋值操作会执行两个操作

  • 首先编译器会在当前作用域声明一个变量(如果没有声明过)
  • 然后在运行时引擎会在作用域中查找(由作用域协助)该变量,如果找到就会对它赋值

LHSRHS 查询

  • LHS : 赋值操作的目标是谁
  • RHS : 谁是赋值操作的源头

  • 当变量出现在赋值操作的左侧时,进行 LHS 查询; 试图找到变量的容器本身,从而对其赋值

  • 出现在右侧进行 RHS 查询; 可以理解为简单的查找某个变量的值,等到某某的值, retrieve his source value(取到它的源值)
1
2
3
4
5
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
1
2
3
4
5
6
7
8
// LHS  3处
c = foo();
a = 2(隐式分配);
b = a;
// RHS 4处
foo(2); 找到foo()函数
var b = a; 找到a的值
return a+b 中, 找到a的值, b的值

1.3 作用域嵌套

  • 作用域是根据名称查找变量的一套规则,实际中通常需要同时兼顾几个作用域
  • 当一个快或函数嵌套在另一个块或函数中时,就发生了作用域嵌套. 所以, 在当前作用域中无法找到某个变量时, 引擎就会在外层嵌套的作用域中继续查找,直到找到该变量, 或抵达最外层的作用域(全局作用域)为止

遍历嵌套作用域链的规则

  • 引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找. 当抵达最外层全局作用域时,无论找到与否, 查找过程都会停止

1.4 异常

  • 在变量没有声明(任何作用域中都无法找到该变量)的情况下, LHS 和 RHS 查询的行为不一样
1
2
3
4
5
function foo(a) {
console.log(a + b);
b = a;
}
foo(2);

当我们第一次对 b 进行 RHS 查询时, 无法找到该变量 , 也就是 b 是一个未声明的变量

  • 如果 RHS 查询在所有嵌套的作用域中找不到该变量, 引擎就会抛出 ReferenceError
  • 当引擎进行 LHS 查询时, 非严格模式下, 如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并返还给引擎
  • 严格模式禁止自动或隐式地创建全局变量

1.5 总结

  • 作用域是一套规则,用于确定在何处以及如何查找变量.如果查找的目的是对变量赋值,那么就会进行 LHS 查询; 如果目的是获取变量的值, 就会使用 RHS 查询.
  • 赋值操作符会导致 LHS 查询. 隐式传参也会导致 LHS 查询 foo(2)==>foo(a)
  • 不成功的 RHS 引用会导致抛出 ReferenceError 异常, 不成功的 LHS 引用会导致自动隐式创建一个全局变量(非严格模式下)

2. 词法作用域

作用域主要有两种工作模型 词法作用域 动态作用域(少,bash,Perl)

1
2
3
4
5
6
7
8
9
10
function foo(){
function bar(a){
var i = 3;
console.log(a+i);
}
for(var i = 0; i<10;i++){
bar(i+2)
}
}
foo()

3. 今天没有看书,没有更新