作用域这边主要是LHS和RHS的寻找,闭包与模块化。
传统语言的编译过程
1、分词/词法分析:分词指将一段字符串破成有意义的(相对于这门语言)小块。比如,考虑这段代码:var a=2;这条程序可能会被破成下面的部分:var , a, =,2,还有; 空白可能成为分词小块也可能不会,这得取决于它是否有含义。
注:分词和词法分析之间的区别既微妙又学术,但关键在于这些小块被明确为有状态的还是无状态的。简单点儿说,如果分词块儿调用的是有状态的词法分析规则,来区分a应该作为一个单独的词法块儿还是仅仅是另外一块词法块儿的一部分,那么这个过程就称作词法分析。(我的理解就是 把字符串分为有意义的tokens这是分词,确定每一小块代码段是应该独立还是应该和另外一块合并成一个token,这是词法分析)。
2、解析:取一段分词过的代码流,然后把它变成一个代表了程序语法结构的嵌套元素树,这个树的名字叫AST(Abstract Syntax Tree)(抽象语法树)。
在这个树状结构的解析中,对于var a=2;可能从最高层——变量声明(VariableDeclaration)开始,然后是它的子节点——标识符(Identifier)(它的值是a),以及另外一个子节点AssignmentExpression的子节点叫NumericLiteral(它的值是2)。
3、代码生成:这个过程是使用AST将代码块变为可执行的代码,这部分的过程很依赖于语言本身,包括它的目标平台等。
所以为了避免在细节里太纠结,我们就直接说上述的AST处理了我们的var a = 2;并且把它变成了一行机器指令——这行指令创造出一个变量a,并且存了一个值进去。
JS的编译发生于执行前的几微秒,由引擎、编译器和作用域共同完成。
引擎:负责编译和执行我们的JavaScript程序。
编译器:一个引擎的朋友,处理解析和代码生成的所有脏活累活(见上一节)。
作用域:引擎的另一个朋友,收集并维护所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以了解当前执行代码的方式
var a=2;编译器实际上将会按下面的步骤处理
1、 遇到var a,编译器要求作用域查看该特定范围集合的变量a是否已存在。 如果是这样,编译器忽略此声明并继续。 否则,编译器要求作用域为该范围集合声明一个名为a的新变量。
2、 编译器接下来为引擎生成其随后要执行的代码,来处理 a = 2的赋值。引擎执行a = 2时,首先会询问作用域在当前作用域范围里面是否存在一个可以访问的变量 a 。如果有,引擎就会使用那个变量。如果没有,引擎就会在其他地方寻找。(见下面的嵌套范围部分)。
如果引擎最终找到一个变量,他将会把值 2 赋给它。否则,引擎会大叫出了一个错误!
当引擎执行编译器为第二步生成的代码的时候,它必须查找变量a,来看其是否已经被声明过,这个查找 在我们的例子中,据说引擎在查找变量a的时候,会执行一个LHS(lefthandside)查找。另一个查找的类型叫做RHS(righthand side)。
一个LHS查找会在一个变量出现在赋值运算符的左边时完成,一个RHS查找会在一个变量出现在赋值运算符的右边时完成。
实际上,更精确来说。为了我们的目的,RHS查找是不可区分的,只需简单地查看一些变量的值,而LHS查找则试图找到变量容器本身,以便它可以分配。
遍历嵌套作用域的简单规则就是:引擎从当前正在执行的作用域开始,查找这里的变量,如果没有找到,就继续往更高一级查找。如果达到了最外层的全局作用域,查找就会停止,不管有没有找到需要的变量。
考虑如下情况:
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
当对b的第一次RHS查找时,并不会找到。这时它是一个”未声明”变量,因为在这个作用域中没有找到。
如果在嵌套作用域的任何地方,对变量进行RHS查找失败时,引擎会抛出一个ReferenceError。注意这里的错误类型是ReferenceError。
相反,如果引擎执行一个LHR查询,并且直到到达全局作用域的时候都没有找到这个变量,当程序没有在”Strict Model”下执行时,全局作用域会创建一个以其命名的变量,并且将其告诉引擎。
“不,以前没有一个,但我是有帮助的,为你创造了一个。”
在ES5中添加的“严格模式”[^ note-strictmode]与正常/轻松/懒惰模式有许多不同的行为。 一个这样的行为是它不允许自动/隐式的全局变量创建。 在这种情况下,将没有从LHS查找中返回的全局适用范围的变量,并且引擎会像RHS案例一样抛出ReferenceError。
现在,如果找到一个用于RHS查找的变量,但是您尝试使用不可能的值进行某些操作,例如尝试执行as-function的非函数值,或引用null或 未定义的值,然后引擎引发一种不同种类的错误,称为TypeError。
ReferenceError是作用域分辨率失败相关的,而TypeError意味着作用域分辨率是成功的,但是针对结果进行了非法/不可能的操作。
js是没有块级作用域的,但从ES3开始trycatch语句可以创建块级作用域,但非常繁琐,ES6开始let可以绑定块级作用域。
变量提升:包括变量和函数在内的所有声明都会在代码执行前首先被处理,而赋值或其他运行逻辑等待执行。