原文地址:http://floatyears.info/javascript-lexical-resolvation
JavaScript解析过程可以分为编译和执行两个阶段。在预编译期,JavaScript解释器将完成对JavaScript代码的预处理,也就是说把JavaScript脚本代码转换成字节码,在执行期,JavaScript解释器借助执行期环境把字节码生成机械码,并按顺序执行,完成程序设计的任务。
预编译
JavaScript是一种解释型语言,而不是一种编译型语言,所谓解释型语言,就是代码在执行时才被解释器一行行动态编译和执行,而不是在执行之前就完成编译,简单说,解释型语言就是边编译边执行,而编译型语言是先编译后执行,两者的操作过程不同。
当程序被编译时,需要一个叫做编译器的程序来完成所有工作,一般编译器可以包括下面组件:
符号表:在其中存储所有的符号信息,如类型、范围等。
词法分析器:其功能是将字符流(即脚本字符串)转换为记号(如关键词、操作等)。
语法分析器:其功能是读取记号流,并建立语法树。
语义检查器:用来检查语法树的语义错误。
中间代码生成器:用来把语法树转换为中间代码。
代码优化器:用来优化中间代码。
代码生成器:用来把中间代码生成二进制字节码。
对JavaScript这类解释型语言来说,通过词法分析和语法分析,并建立语法树之后,就开始解释执行了,而不是完全生成字节码之后,再调用虚拟机来执行这些编译好的字节码。
词法分析和语法分析不是完全独立的,而是交错进行的,也就是说,词法分析器不会读取所有的词法记号,然后再使用语法分析器来处理,通常情况下,每取一个词法记号,就送入语法分析器进行分析:
词法分析是对JavaScript脚本代码进行逐一分析的过程,它相当于语言翻译,例如,把英文逐句逐词地译成中文,英文就是源代码,而中文就是代码的记号了。
语法分析的过程就是把词法分析所产生的记号生成语法树,通俗地说就是把从程序中收集的信息存储到数据结构中,请注意,编译中的数据结构包括两种:符号表和语法树。
符号表:就是在程序中用来存储所有符号的一个表,包括所有的字符变量、直接量字符串,还有函数和类。
语法树:就是程序结构的一个树形表示,并将使用这个树形结构来生成中间代码。
例如:下面是一个简单的条件结构和输出信息代码段,语法分析器转换为语法树之后的情形。
1
2
3
4
5
6
7
|
if ( typeof a == "undefined" ){
a = 0;
}
else {
a = a;
}
alert(a);
|
当JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误,并结束整个代码块的解析。
执行期
经过编译阶段的准备,JavaScript代码在内存中已经被构建为语法树,然后JavaScript引擎就会根据整个语法树结构边解释边执行了。
在解释过程中,JavaScript引擎是严格按照着作用域机制来执行的,JavaScript语法采用的词法作用域,也就是说JavaScript的变量和函数作用域是在定义时决定的,而不是采用执行时决定,由于词法作用域取决于源代码结构,所有JavaScript解释器只需要通过静态分析就能确定每个变量、函数的作用域,这种作用域也称为静态作用域。
当JavaScript解释器执行每个函数时,先创建一个执行环境,在这个虚拟环境中创建一个调用对象,在这个对象内存储着当前作用域中所有变量、参数、嵌套函数、外部引用和父级引用列表upvalue等语法分析结构。
实际上,通过声明语句定义的变量和函数在预编译期的语法分析中就已经存储到符号表了,然后把他们与调用对象中的同名属性进行映射(赋值)即可。调用对象的生命周期与函数的生命周期是一致的,当函数调用完毕且没有外部引用的情况下,会自动被JavaScript引擎当做垃圾进行回收。
另外JavaScript解释器通过作用域链把多个嵌套的作用域串联在一起,并借助这个链条帮助JavaScript解释器检索变量值。这个作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系,当JavaScript解释器检索变量的值,会按着这个索引编号来进行快速查找,知道找到全局对象为止。如果没有找到值,则传递一个特殊的undefined值。
如果函数引用了外部变量的值,则JavaScript解释器会为该函数创建一个闭包体,闭包体是一个完全封闭和独立的作用域,它不会在函数调用完毕后就被JavaScript引擎当做垃圾进行回收,闭包体可以长期存在,因此开发人员常把闭包体当做内存中的蓄水池,专门用来长期保存变量的值。
参考资料:《JavaScript征途》。
下面这幅图可以大致说明整个解析流程: