[译] 图解JS: 4. JavaScript引擎

112 阅读5分钟

JavaScript Visualized(7 Part Series): 4. the JavaScript Engine - By Lydia Hallie

JavaScript很酷(别艾特我),但是一个机器是怎么理解你写的代码的?作为JavaScript开发者,我们通常不需要自己处理编译器。然而,了解JavaScript引擎的基本知识以及他是如何将对人类友好的JS代码转成机器能理解的形式,对我们来说显然是非常有好处的!🥳

注意:本文基于Node.js和基于Chromium浏览器使用的V8引擎。


HTML解析器遇到带有src的script标签时会暂停解析,然后通过网络请求、缓存或已安装的Service Worker中加载脚本文件。字节流解码器(byte stream decoder)负责处理返回的脚本内容(响应是以字节流形式返回的)。在下载脚本的时候字节流解码器就会解码该字节流。

1.gif


字节流解码器从解码后的字节流中获取每个token。例如:0066解码为f, 0075u, 006en, 0063c, 0074t, 0069i, 006fo以及006en,后面还有一个空格,看起来你写了一个function!这是JavaScript中的一个保留关键词,创建一个token,然后将它发送给解析器(以及预解析器,没包含在下面的动图中,不过稍后会解释)。剩余的字节流都会以同样的方式被处理。

2.gif


JS引擎使用两个解析器:预解析器(preparser)和解析器(parser)。为了减少加载网站的时间,JS引擎只会加载立马要用的那些代码。预解析器处理那些可能稍后要用到的代码,而解析器只处理马上要用的代码。如果一个函数是在用户点击按钮时才触发,那它就没必要在加载网页时立马被编译。当用户最终点击了按钮需要用到那些代码时,对应的函数才会被传给解析器。

解析器根据从字节流解码器那收到的tokens构建节点,然后使用这些节点构造一棵抽象语法树(Abstract Syntax Tree, AST.)🌳

3.gif


接下来就轮到解释器(interpreter)工作了。解释器会遍历抽象语法树,并根据其上的信息生成字节码。当字节码生成完毕后,抽象语法树就会被删除,释放其所占的内存空间。现在机器终于能理解并执行这些代码了!🎉

4.gif


虽然字节码很快,但它可以更快。在执行字节码时会记录一些信息。例如检测某些行为是否经常发生,以及所使用的数据类型。也许你已经调用一个函数几十次了:那么是时候优化它了,这样它会运行得更快!🏃🏽‍♀️

字节码和生成的类型反馈一起被发送到优化编译器(optimizing compiler)。优化编译器获取字节码和类型反馈,并从中生成高度优化的机器代码。🚀

5.gif


JavaScript是一个动态类型语言,这意味着数据的类型可以不断变化。但是如果JavaScript引擎每次都要去检查一个数据的类型的话那执行速度就会很慢。

为了缩短其解释代码的时间,优化编译器只会处理那些引擎在执行字节码时遇到过的东西。如果我们重复使用一段返回相同数据类型的代码,那就可以直接使用优化后的机器码来加速。然后,由于JavaScript是动态类型语言,相同的一段代码突然返回一个不同类型的数据的情况也偶尔发生。如果这种情况发生了,那机器码就会还原(de-optimized),引擎就会回退使用原始的字节码。

也就是说如果一个函数被调用了100次,并且每次都返回相同的值。那JS引擎就会假设在第101次调用该函数的时候也会返回这个值。

假设我们有下面的加和函数sum, 并且到目前为止总是传递两个数值型的值作为参数来调用该函数:

image.png

上面的调用会返回结果3!下次再调用sum时,引擎会假设我们还会用两个数值型变量调用它。

如果假设正确,则不需要动态查找,可以直接重用优化的机器代码。否则,它将恢复到原始的字节代码。

例如,当我们下次传递两个字符串调用sum函数时,由于JavaScript时动态类型语言,所以我们可以得到正确的返回值。

image.png

数字2会被转换成字符串,该函数会返回字符串12。它会解释原始的字节码并更新类型反馈。


希望这篇文章对你有帮助!😊当然,这篇文章没有涉及更多JS引擎相关的内容(JS堆、调用栈等),后续可能会涉及!如果你对JS的原理感兴趣我非常推荐你去进行一些深入研究,V8是开源的并且关于它是如何工作的有非常好的文档。🤖

FAQ: 我用Keynote制作的这些动图然后截屏记录的哈哈哈。非常感谢也支持大家把这篇文章翻译成其他语言!只是请记得保留原文链接并且留言告知我!😊