1.1、编译原理
js作为我们口中常说的“动态”或者“解释执行”语言,同时事实上他是一门编译语言,它与传统的编译一言非常相似,甚至更复杂。
一般来说,传统的编译语言编译过程中会经历三个步骤:
-
1.分词/词法分析
-
解析/语法分析
-
代码生成
而js的编译一般发生在执行前的几微秒。
1.2、理解作用域
首先我们要知道在编译过程中,一共有三个角色参与进来,引擎、编译器和作用域
引擎负责了整个代码的编译及执行过程,编译器负责语法分析及代码生成等,作用域则收集和维护好所有声明的标识符。
举例:
var a = 2;
编译器首先会找作用域询问是否有名称为a的变量,有则忽略,否则会让作用域进行申明,
接下来编译器会为引擎生成运行时所需要的代码,也就是a = 2; (换句话说变量申明操作是由编译器做的,引擎只需要进行赋值操作)
随后引擎开始工作,它会找到作用域询问a变量是否存在,如果有,则使用这个变量,否则会继续往上级作用域进行查找(关于上级作用域在1.3的作用域嵌套中解释)。
最终如果找到a则引擎会进行赋值操作,否则会抛出异常。
总结:编译器负责对变量进行申明处理,引擎负责去作用域找申明的变量进行使用;
1.3、引擎的查询方式
首先,引擎分为LHS跟RHS两种查询方式,L与R可以理解成赋值操作的左侧与右侧。
换句话说,当进行变量赋值时,进行LHS查询,如a = 2;当进行变量值查找时,进行RHS查询。如console.log(a);此时的a需要进行RHS查询。
注意:这种查询规则的定义在函数申明时并不适用。如function foo (a) {...},正常理解为var foo; foo = func...,那么这里的foo会进行一个LHS查询,事实上编译器在代码生成的同时,已经将foo的申明和值的定义同时处理了,所以在引擎执行代码时,不会再对foo进行赋值操作,固不会产生LHS查询。
1.4、作用域嵌套
当一个块或者函数在另一个块或者函数中,就发生了作用域的嵌套,当引擎无法在当前作用域中找到某个变量,则会一直往上级查找,直到找到该变量或抵达全局作用域中为止。不论LHS与RHS皆是如此。
当RHS查询在所有嵌套的作用域中未找到指定变量则会抛出ReferenceError的错误,而LHS查询找不到时则会自动在全局作用域中创建该变量并返回给引擎(非严格模式)。
如果RHS找到变量但对其进行不适当的操作(比如执行非函数类型变量),会抛出TypeError类型错误。
也就是说ReferenceError与作用域判别失败相关,TypeError则代表作用域判别成功,但操作不合法。
综上这些细节来看,区分LHS跟RHS是非常重要的。
测验:
请找出以下代码中的三处LHS与四处RHS。
function foo(a){
var b = a;
return a + b;
}
var c = foo(2)