2. scope.md

Scope 作用域

overview

  1. Nested Scope
  2. Hosting
  3. Closure
  4. Modules

当你遇到作用域相关的问题,回答两个问题

  1. What position is it in? (该变量在哪个位置)
  2. What scope does it belong to? (该变量在那个作用域)

js是一种编译型(解析型)语言: 当在逐行执行代码之前,js引擎会先扫描一次代码, 如果你有任何的语法错误,js会抛出错误。(所以它在执行语句的时候就编译了一次?)

在编译理论中: 编译是高级语言编写转换成机器可执行代码的处理过程

  1. lexing and tokenization.(就是把代码转成抽象语法树AST: abstract syntax tree)

  2. code generation(把AST转换成计算机可执行的代码)

    很多观点认为, js是即时编译的, 就像薛定谔的猫,如果你在第十行有个语法错误,那么你在运行到第十行才发现。这个观点是错误的, 因为,如果我们第十行有错误,那么1-9行都不会执行。并且抛出错误.

所以,在js执行之前,会先走一次预编译, 这次编译会生成AST.其实这和那些静态语言类似, 比如java.js并不是运行时编译的即时编译语言.

函数和块作用域一起,组成了js的scope.

compilation & Scope

在编译阶段,我们有个scope管理器和一个编译器。
编译过程中,scope管理器中会维护一个作用域, 每个作用域里存放变量,以及变量的初始值undefined。在处理完所有的作用域时, 我们处理后的信息移交给js引擎, 用于执行。

所以,js所获得关于scope的信息,是在第一次编译的时候获得的,然后在运行时使用这些信息, 但是这些信息是在编译的时候确定的.

这样有一个好处,就是我在编译阶段获取到的信息可以有很多优化的空间。

excute code

编译阶段与执行阶段是两个不同的过程。

在编译阶段,我们更多的去处理的是定义语句(LHS): var/let/const/funcion

除此之外,剩下的都是执行阶段要做的事.

let a = 1;

let a is LHS
= 1 IS RHS

在执行阶段,js引擎获得了变量的作用域信息(这些引用信息其实就是每个变量现在的内存地址, 代码执行的时候其实也就是不断的往内存里放东西),就开始执行, 关于变量,还有定义的信息,都从编译阶段获取的scope信息中获取.(我们写完代码后,作用域是不会变的)

查找顺序, 本级作用域-> 父级作用域->父级的父级作用域-> …… -> 全局作用域 -> undefined

所以在js运行的时候分两个阶段,1. 获取变量(函数也是一种特殊的变量)信息, 也就是预编译的过程 2. 根据这些信息执行运算(执行代码内容)

lexical scope review

js不是interpreted(解释型)语言和逐行执行的语言。逐行执行只是在他运行的第二阶段的执行顺序。js是编译型语言,编译过程分为两个阶段: 1. 在LHS切分作用域, 获取变量信息(对应的东西放进对应的篮子里), 在这个阶段我们可以提前发现错误, 完成信息收集后,把收集到的信息传递给js引擎(由于我们提前获取到了信息,在我们的执行阶段可以进行进一步的优化) 2. 拿到变量信息,开始执行RHS, 执行阶段使用阶段1获取的信息,进行运算

基于它的编译模型, 也就不奇怪为啥所有变量一开始的初始值都是undefied了。

现在我认为,我们的代码就是往内存中放东西。

Dynamic global variables

1
2
3
4
5
6
7
8
function a() {
c = 0;
}
c // throw error

a()

c // 0

Js的历史原因导致了这种动态全局变量的产生.尽可能的避免产生这种全局变量.

nested scope

嵌套scope的分析和单层scope分析一样,但是多层嵌套还是会减小代码的可读性,让代码不清晰.

error type

  1. reference error: 函数调用产生
  2. undefined error: 变量没有定义产生

undefined and undeclared

undefined: 你定义了一个变量而为赋值, 那么这个变量就是undefined

undeclared: 你使用了一个变量(访问了也算),但是这个变量在内存中找不到(也就是在编译的第一阶段,收集变量信息的时候就报错了)