JavaScript百炼成仙 1.19 JavaScript编译原理

时间:2022-10-31 12:00:44


 前些天发现了一个巨牛的人工智能学习博客,通俗易懂,风趣幽默,忍不住分享一下给大家。​​点击跳转​

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

就连林元青都有些看不下去的,笑着说道:

“那你就长话短说吧!”

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

var a = 10;

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

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

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

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

“第二个步骤是解析,由JavaScript编译器去将刚才分词得到的一个个代码块进行解析,生成一棵抽象的语法树(AST)。

这么讲的话有点难以理解,简单来说,JavaScript代码是没有办法直接运行的,要运行JavaScript代码,就需要由JavaScript编译器对其进行编译,只有编译之后的代码才可以被识别,然后再由JavaScript引擎去执行代码逻辑。

但是,由于JavaScript这门编程语言的特殊性,编译的过程一般就在代码执行前的几个微秒之内,甚至更短。所以,直观地看,编译和运行是同时发生的。

或者说,我们根本感受不到编译的存在。就比如刚才的例子。var a = 10; 编译的过程实在是太短太短了,我们根本就感觉不到编译的存在。

但其实JavaScript引擎早在我们运行这段代码的时候,就已经完成了编译,然后立刻就做好了要执行代码的准备。”

“那你说的抽象语法树是怎样的?”

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

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

var a;

console.log(a);

JavaScript百炼成仙 1.19 JavaScript编译原理

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

JavaScript百炼成仙 1.19 JavaScript编译原理

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

PS:抽象语法树的创建可以在网站​http://esprima.org/demo/parse.html​上自行调试和验证。