从浏览器到JS系列(一)JS引擎,JS虚拟机,JS运行时(3)

1.JS引擎的组成部分

2.什么是解释器和编译器

3.什么是JIT

接上一篇剩下的问题:

1.JS引擎的组成部分

这里只简单介绍一下JS引擎的组成部分,后续会较为完整,系统地介绍JS引擎及其工作机制

一个JS引擎大概包含以下几个部分。

————浏览器内核分析7 -- JavaScript引擎 

  • 编译器:将源码转变为抽象语法树,某些引擎还将语法树转变为了字节码。编译过程会占用用户时间。
  • 解释器:接收字节码,执行它。需要处理内存分配,动态优化等
  • JIT:运行时优化,热点代码字节码转变为本地代码。和Java中的JIT类似。如果优化没有达到效果,还会回退为优化前代码。
  • 垃圾回收器GC和分析工具,分析工具收集运行时信息,如热点代码探测。 

我刚看到这段的时候感觉挺惊讶的,因为印象中JS一直是一门解释型语言,应该只有解释器才是,为什么会有编译器呢,JS源码不是由解释器一行一行解释执行的吗。后来查阅了更多的资料后感觉自己此前的想法有点狭隘也有点想当然了。这里再引用此前引用过的一段作以说明:

最后回到JavaScript语言上来。前面已经说了它是一种解释性脚本语言。是的,它的确是,但是随着众多工程师不断投入资源来提高它的速度,这使得它能够使用了Java虚拟机和C++编译器中众多的技术,它的工作方式也在演变。早期也是一样由解释器来解释它们即可,就是将源代码转变成抽象语法树,然后在抽象语法树上解释执行。随着将Java虚拟机的JIT技术引入,现在的做法是将抽象语法树转成中间表示(也就是字节码),然后通过JIT技术转成本地代码,这能够大大的提高了执行效率。当然也有些做法直接从抽象语法树生成本地代码的JIT技术,例如V8

————什么是JavaScript引擎

上面这段已经说得很清楚了,早期JS由解释器解释执行,即将源代码转变为抽象语法树,然后直接在抽象语法树上解释执行。而后是将源码转译为中间表示(字节码)执行,这个过程其实就已经是包含了编译。编译,是从源代码(通常为高级语言)到能直接被计算机或虚拟机执行的目标代码(通常为低级语言或机器语言)的翻译过程,而字节码正是虚拟机所能执行的的目标代码。而Chrome的V8干脆就只由编译器组成:

Google 的 V8 引擎 是用 C++ 编写的,它也能够编译并执行 JavaScript 源代码、处理内存分配和垃圾回收。它被设计成由两个编译器组成,可以把源码直接编译成机器码:

———— 一篇给小白看的 JavaScript 引擎指南 

  1. Full-codegen:输出未优化代码的快速编译器 
  2. Crankshaft: 输出执行效率高、优化过的代码的慢速编译器

但v8 5.9 发布后,其中的 Ignition 字节码解释器将默认启动。即V8不再是直接将源码编译成机器码,而是选择加入了中间码处理。详情可点击这里V8 Ignition:JS 引擎与字节码的不解之缘 

下面再引两段说明一下JS引擎和解释器,编译器的关系

简单地说,JavaScript解析引擎就是能够“读懂”JavaScript代码,并准确地给出代码运行结果的一段程序。比方说,当你写了 var a = 1 + 1; 这样一段代码,JavaScript引擎做的事情就是看懂(解析)你这段代码,并且将a的值变为2。

学过编译原理的人都知道,对于静态语言来说(如Java、C++、C),处理上述这些事情的叫编译器(Compiler),相应地对于JavaScript这样的动态语言则叫解释器(Interpreter)。这两者的区别用一句话来概括就是:编译器是将源代码编译为另外一种代码(比如机器码,或者字节码),而解释器是直接解析并将代码运行结果输出。 比方说,firebug的console就是一个JavaScript的解释器。

但是,现在很难去界定说,JavaScript引擎它到底算是个解释器还是个编译器,因为,比如像V8(Chrome的JS引擎),它其实为了提高JS的运行性能,在运行之前会先将JS编译为本地的机器码(native machine code),然后再去执行机器码(这样速度就快很多),相信大家对JIT(Just In Time Compilation)一定不陌生吧。

————我们应该如何去了解JavaScript引擎的工作原理

“解释器”到底是什么?“解释型语言”呢? 

很多资料会说,Python、Ruby、JavaScript都是“解释型语言”,是通过解释器来实现的。这么说其实很容易引起误解:语言一般只会定义其抽象语义,而不会强制性要求采用某种实现方式。 
例如说C一般被认为是“编译型语言”,但C的解释器也是存在的,例如Ch。同样,C++也有解释器版本的实现,例如Cint。 
一般被称为“解释型语言”的是主流实现为解释器的语言,但并不是说它就无法编译。例如说经常被认为是“解释型语言”的Scheme就有好几种编译器实现,其中率先支持R6RS规范的大部分内容的是Ikarus,支持在x86上编译Scheme;它最终不是生成某种虚拟机的字节码,而是直接生成x86机器码。 

解释器就是个黑箱,输入是源码,输出就是输入程序的执行结果,对用户来说中间没有独立的“编译”步骤。这非常抽象,内部是怎么实现的都没关系,只要能实现语义就行。你可以写一个C语言的解释器,里面只是先用普通的C编译器把源码编译为in-memory image,然后直接调用那个image去得到运行结果;用户拿过去,发现直接输入源码可以得到源程序对应的运行结果就满足需求了,无需在意解释器这个“黑箱子”里到底是什么。 
实际上很多解释器内部是以“编译器+虚拟机”的方式来实现的先通过编译器将源码转换为AST或者字节码然后由虚拟机去完成实际的执行所谓“解释型语言”并不是不用编译,而只是不需要用户显式去使用编译器得到可执行代码而已。 

————虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 

你可能感兴趣的:(JS)