JavaScript解析机制

1.5  JavaScript解析机制

JavaScript解 析过程可以分为编译和执行两个阶段。编译也就是我们常说的JavaScript预处理(即预编译)。在预编译期,JavaScript解释器将完成对 JavaScript代码的预处理,也就是说把JavaScript脚本代码转换成字节码。在执行期,JavaScript解释器借助执行期环境把字节码 生成机械码,并按顺序执行,完成程序设计的任务。

1.5.1  预编译

JavaScript是一种解释型语言,而不是编译型语言。所谓解释型语言,就是代码在执行时才被解释器一行行动态编译和执行,而不是在执行之前就完成编译。简单说,解释型语言就是边编译边执行,而编译型语言是先编译后执行,两者的操作过程不同。

当程序被编译时,需要一个叫做编译器的程序来完成所有工作。一般编译器可以包括下面组件(如图1-3所示)。

? 符号表:在其中存储所有的符号及其信息,如类型、范围等。

? 词法分析器:其功能是将字符流(即脚本字符串)转换为记号(如关键词、操作符等)。

? 语法分析器:其功能是读取记号流,并建立语法树。

? 语义检查器:用来检查语法树的语义错误。

? 中间代码生成器:用来把语法树转换为中间代码。

? 代码优化器:用来优化中间代码。

? 代码生成器:用来把中间代码生成二进制字节码。

图1-3  编译器构成和工作流程示意图

JavaScript解析机制

根 据上图,我们可以看到,程序的一般编译步骤分为:词法分析、语法分析、语义检查、代码优化和生成字节码。但是,对于JavaScript这类解释型语言来 说,通过词法分析和语法分析,并建立语法树之后,就开始解释执行了,而不是完全生成字节码之后,再调用虚拟机来执行这些编译好的字节码。

在词法分析过程中,JavaScript解释器先把脚本代码的字符流转换为记号流,例如:

把字符流:

a = (b - c);

转换为记号流:

NAME "a"

EQUALS

OPEN_PARENTHESIS

NAME "b"

MINUS

NAME "c"

CLOSE_PARENTHESIS

SEMICOLON

词法分析器是编译器中与源程序直接接触的部分,因此词法分析器可以实现:

? 去掉注释,自动生成文档。

? 提供错误位置(可以通过记录行号来提供),当字符流变成词法记号流以后,就没有了行的概念。

? 完成预处理,如C语言中的宏定义等。

词法结构是JavaScript语言基础(详细讲解请参阅第3章内容),至于词法分析的实现就比较复杂,这里就不再深入研究,读者只需要简单了解它的工作机制即可。

词法分析和语法分析不是完全独立的,而是交错进行的,也就是说,词法分析器不会读取所有的词法记号,然后再使用语法分析器来处理,通常情况下,每取一个词法记号,就送入语法分析器进行分析(如图1-4所示)。

图1-4  词法分析和语法分析示意图

JavaScript解析机制

词法分析是对JavaScript脚本代码进行逐一分析的过程,它相当于语言翻译,例如,把英文逐词逐句地译成中文,英文就是源代码,而中文就是代码的记号了。

语法分析的过程就是把词法分析所产生的记号生成语法树。通俗地说就是把从程序中收集的的信息存储到数据结构中。请注意,编译中的数据结构包括两种:符号表和语法树。

? 符号表:就是在程序中用来存储所有符号的一个表,包括所有的字符串变量、直接量字符串,还有函数和类。

? 语法树:就是程序结构的一个树形表示,并将使用这个树形结构来生成中间代码。

例如,下面是一个简单的条件结构和输出信息代码段,被语法分析器转换为语法树之后,如图1-5所示。

if(typeof a == "undefined" ){

   a = 0;

}

else{

   a = a;

}

alert(a);

图1-5  语法树结构示意图

JavaScript解析机制

当 JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误,并结束整个代码块的解析。对于传统强类型语言来说,通过语法分析,构 造出语法树后,翻译出来的句子可能还会有模糊不清的地方,还需要进一步的语义检查。语义检查的主要部分是类型检查,例如,函数的实参和形参类型是否匹配。 但是,对于弱类型语言来说,这一步就没有了。

1.5.2  执行期

经过编译阶段的准备,JavaScript代码在内存中已经被构建为语法树,然后JavaScript引擎就会根据这个语法树结构边解释边执行了。

在 解释过程中,JavaScript引擎是严格按着作用域机制来执行的。JavaScript语法采用的是词法作用域,也就是说JavaScript的变量 和函数作用域是在定义时决定的,而不是执行时决定的,由于词法作用域取决于源代码结构,所以JavaScript解释器只需要通过静态分析就能确定每个变 量、函数的作用域,这种作用域也称为静态作用域。

当JavaScript解释器执行每个函数时,先创建一个执行环境,在这个虚拟环境中创建一个调用对象,在这个对象内存储着当前域中所有局部变量、参数、嵌套函数、外部引用和父级引用列表upvalue等语法分析结构。

实 际上,通过声明语句定义的变量和函数在预编译期的语法分析中就已经存储到符号表中了,然后把它们与调用对象中的同名属性进行映射即可。调用对象的生命周期 与函数的生命周期是一致的,当函数调用完毕且没有外部引用的情况下,会自动被JavaScript引擎当做垃圾进行回收。

另 外,JavaScript解释器通过作用域链把多个嵌套的作用域串连在一起,并借助这个链条帮助JavaScript解释器检索变量的值。这个作用域链相 当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止, 如果没有找到值,则传递一个特殊的 undefined值。

如果函数引用了外部变量的值,则 JavaScript解释器会为该函数创建一个闭包体,闭包体是一个完全封闭和独立的作用域,它不会在函数调用完毕后就被JavaScript引擎当做垃 圾进行回收。闭包体可以长期存在,因此开发人员常把闭包体当做内存中的蓄水池,专门用来长期保存变量的值。

只有当闭包体的外部引用被全部设置为null值时,该闭包才会被回收。当然,也容易引发垃圾泛滥,甚至出现内存外溢的现象。

来源:http://hi.baidu.com/zhoumm1008/blog/item/0a968a8570696fc29023d930.html

加支付宝好友偷能量挖...


评论(0)网络
阅读(147)喜欢(0)JavaScript/Ajax开发技巧