购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.19 JavaScript编译原理

“谈到Javascript代码的运行机制,那可就说来话长了。”叶小凡学着长辈的口气,一脸欠揍的表情。

就连林元青都有些看不下去了,笑着说道:“那你就长话短说吧!”

“是,弟子遵命。先来看一个最简单的例子。”说着,叶小凡随手就打出了一段代码。

     var a = 10;

“叶小凡,你这是在逗我吗,这么简单的代码谁看不懂?”对面的弟子感到有些不耐烦。

“师兄,你先别急,没错,这无非就是一个简单的定义语句,可是你知道它内部的原理吗? JavaScript代码在运行之前会经过一个编译的过程,而编译有三个步骤 。”叶小凡不紧不慢地说道。

“哦,小娃娃,你可好好说说是哪三个步骤?”尹曾琪也来了兴趣,因为身为掌尊的他也是头一次听到这个说法。

第一个步骤是分词 ,JavaScript代码其实就是由一句句话组成的,分词的目的是把这些代码分解为一个个有意义的代码块。比如刚才的例子,如果经过分词的步骤,那么得到的结果就是‘var、a、=、2、;’。”

第二个步骤是解析 ,由JavaScript编译器对刚才分词得到的一个个代码块进行解析,生成一棵抽象的语法树(AST)。简单来说,JavaScript代码是没有办法直接运行的,要想运行JavaScript代码,就需要通过JavaScript编译器对其进行编译,只有编译之后的代码才可以被识别,然后通过JavaScript引擎执行代码逻辑。但是,由于JavaScript这门编程语言的特殊性,其编译的过程一般就在代码执行前的几微秒甚至更短的时间之内。所以直观地看,编译和运行是同时发生的,或者说我们根本感觉不到编译的存在。就比如刚才的例子。‘var a=10;’的编译过程实在是太短了,我们根本就感觉不到编译的存在。但其实JavaScript引擎早在我们运行这段代码的时候就已经完成了编译,然后立刻做好了要执行代码的准备。”

“那你说的抽象语法树是什么啊?”

“抽象语法树定义了代码本身,通过操作这棵树可以精准地定位到赋值语句、声明语句和运算语句。”叶小凡不紧不慢地说道。

“再来说说刚才的代码,很明显,这是一个赋值语句,当然,这也是一个定义的语句。我们通过JavaScript的解析器把它解析为一棵抽象树。”

图1-12 抽象树

效果如图1-12所示。

“让我们一个一个来看,首先是最顶层的大节点,也就是这棵树的顶端,上面清清楚楚地写着Program body,代表我们写的代码是一个程序。然后看这个程序里面的第一个也是唯一的一个子节点,上面清清楚楚地写着VariableDeclaration,意思就是变量声明。哦,这就很明白了,‘var a = 10;’这句话是一个程序,程序的目的是进行一个变量的声明。现在,让我们展开这个子节点,看看里面还有什么玄奥。”

效果如图1-13所示。

“在VariableDeclaration节点中包含两个子节点,一个是declarations[1],另一个是kind。declarations[1]是声明数组,中括号里面写了一个1,表示这个语句只声明了一个变量。kind代表种类,表示用var关键字声明一个变量,我想到这一步,应该没有什么问题吧。”

效果如图1-14所示。

图1-13 展开子节点(1)

图1-14 展开子节点(2)

“继续展开declarations[1]节点,发现有一个VariableDeclarator节点,它也表示变量声明,正因为上一个父节点是declarations[1],‘[1]’表示里面只有一个声明,因此展开后里面也只有一个子节点。”

效果如图1-15所示。

“好,终于看到变量声明的具体信息了,可以看到里面分为id和init两个子节点,id代表变量名,identifier是标识符,代表我们的变量名,也就是a。init表示变量的初始化操作,从语句上也能看出,它是将10赋给变量a。”

“如果我把代码换一下,不把10赋值给a,看看会怎样?”叶小凡嘿嘿一笑,卖了个关子,随后又打出一段代码,并且用JavaScript Parser解释了一下。

效果如图1-16所示。

图1-15 展开子节点(3)

图1-16 展开子节点(4)

“如果没有给变量a赋值,那么JavaScript的解释器也会给变量a赋一个初始值,null代表空。注意:这里的null不要理解为JavaScript里面的数据类型null,而是语义上的空。实际上,在代码执行的时候,变量a的值是undefined。接下来,我们看看如果输出一个变量a会发生什么。”

     var a;
     console.log( a);

效果如图1-17所示。

“现在和刚才不同,代码中多了一个console.log输出语句。在生成的抽象语法树上,又结出了一个新的果实——ExpressionStatement(表达式语句)。表达式语句就是普遍意义上的一行JavaScript代码。console是一个内置对象,log是console对象的一个方法,变量a作为参数传入了log方法。总体来说,这就是一个函数的调用语句。下面我们来看看这个表达式语句的抽象语法树。”

图1-17 展开子节点(5)

图1-18 抽象语法树

效果如图1-18所示。

“接下来讲最后一个步骤,就是 代码生成 。在这个过程中,JavaScript引擎会把在第二个步骤中生成的抽象语法树进行转换,转换成什么呢?没错,就是可执行的代码。也许最终生成出来的就是一些机器指令,创建了一个叫作a的变量并放在变量区,然后分配一些内存以存放这个变量,最后将数字10存储在了变量a所在的地方。”

小提示:抽象语法树的创建可以在网站 http :// esprima.org / demo / parse.html 上自行调试和验证。 d5Ue+pCpy5X5FSoOYKlk+aZPSscv7gXa4PWDgs4bXtctEouCIpW/wPuUJYMAd7AJ

点击中间区域
呼出菜单
上一章
目录
下一章
×