浅析V8引擎,让你更懂JavaScript!

云加社区

共 2765字,需浏览 6分钟

 ·

2022-03-19 03:46


导语 | 本文介绍了编译、解释、动静态语言等基本概念,以及V8引擎的基本流程。本文将对其进行详细阐述,希望为更多的开发者提供经验和帮助。


一、编译与解释


二进制指令就是机器码:


  • 编译:将源代码一次性转换成目标代码的过程。执行编译过程的程序叫编译器(Compiler)。


  • 解释:将源代码逐条转换成目标代码,同时逐条运行的过程。执行解释过程的程序叫解释器(Interpreter)。解释器一般来说就是vm,vm有两种,一种是基于堆栈,一种是基于寄存器。


编译过程大致包括词法分析、语法分析、语义分析、性能优化、生成可执行文件等五个步骤,期间涉及到复杂的算法和硬件架构。解释器与此类似。



二、静态语言与动态语言


高级语言按照执行方式的不同,可分为静态语言动态语言


静态语言:使用编译执行的语言,如C、C++、Golang等。使用编译器一次性生成目标代码,“一次编译,无限次运行”,程序运行速度更快。编译型语言一般是不能跨平台的,也就是不能在不同的操作系统之间随意切换。


动态语言:使用解释执行的语言,如Python、Javascript、PHP等。执行过程中需要源代码,只要存在解释器,源代码可以在任何操作系统上运行,可移植性好,“一次编写,到处运行”。



解释型语言之所以能够跨平台,是因为有了解释器这个中间层。在不同的平台下,解释器会将相同的源代码转换成不同的机器码,解释器帮助我们屏蔽了不同平台之间的差异。


java和C#是一种比较奇葩的存在,它们是半编译半解释型的语言,源代码需要先转换成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中执行。Java引领了这种风潮,它的初衷是在跨平台的同时兼顾执行效率;C#是后来的跟随者,但是C#一直止步于Windows平台,在其它平台鲜有作为。


总结



三、V8引擎


Javascript是解释型语言,那么V8引擎就对应着解释器。但是V8引擎为了提高JS的运行效率,会提前编译。


也就是V8引擎包括两个阶段:编译、执行,编译阶段指V8将JavaScript转换为字节码或者二进制机器码,执行阶段指解释器解释执行字节码,或者CPU直接执行二进制机器码。


(一)JIT


V8引擎同时采用了解释执行和编译执行这两种方式,也就是在运行时进行编译,这种方式称为JIT (Just in Time) 即时编译。


V8在执行JavaScript源码时,会先通过解析器将源码解析成AST,解释器会将AST转化为字节码,一边解释一遍执行。


解释器同时会记录某一代码片段的执行次数,如果执行次数超过了某个阈值,这段代码便会被标记为热代码(Hot Code),同时将运行信息反馈给优化编译器TurboFan,TurboFan根据反馈信息,会优化并编译字节码,最后生成优化的机器码。



(二)Parser生成抽象语法树


Parser生成AST抽象语法树过程包括语法分析、词法分析,和Babel等工具差不多。


生成AST中的一个优化是惰性解析(Lazy Parsing),因为源码在执行前如果全部完全解析的话,不仅执行时间过长,而且会消耗更多的内存。


惰性解析就是指如果遇到并不是立即执行的函数,只会对其进行预解析(Pre-Parser),当函数被调用时,才会对其完全解析。


预解析时,只会验证函数的语法是否有效、解析函数声明以及确定函数作用域,并不会生成AST,这项工作由Pre-Parser预解析器完成。



(三)Ignition生成字节码


字节码是机器码的抽象,可以看作是小型的构建块。相比机器码,字节码不仅占用内存少,而且生成字节码的时间很快,提升了启动速度。


另外,字节码与特定类型的机器码无关,通过解释器将字节码转换为机器码后才可以执行,这样也使得V8更加方便的移植到不同的CPU架构。


可以通过如下命令,查看JavaScript代码生成的字节码。


node --print-bytecode index.js


注意,解释器执行字节码前,还是会将字节码转为机器码,因为计算机只识别机器码。



(四)TurboFan


Ignition执行上一步生成的字节码,并记录代码运行的次数等信息,如果同一段代码执行了很多次,就会被标记为 “HotSpot”(热点代码),然后把这段代码发送给 编译器TurboFan。


然后TurboFan把它编译为更高效的机器码储存起来,等到下次再执行到这段代码时,就会用现在的机器码替换原来的字节码进行执行,这样大大提升了代码的执行效率。


另外,当TurboFan判断一段代码不再为热点代码的时候,会执行去优化的过程,把优化的机器码丢掉,然后执行过程回到Ignition。


TurboFan做的优化包括内联(inlining)和逃逸分析(Escape Analysis)。


内联就是将相关联的函数进行合并,减少运行时间。比如:


function add(a, b) {  return a + b}function foo() {  return add(2, 4)}


内联处理后:


function fooAddInlined() {  var a = 2  var b = 4  var addReturnValue = a + b  return addReturnValue}
// 因为 fooAddInlined 中 a 和 b 的值都是确定的,所以可以进一步优化function fooAddInlined() { return 6}


逃逸分析就是分析对象的生命周期是否仅限于当前函数,如果是的话会对其进行优化。比如:


function add(a, b){  const obj = { x: a, y: b }  return obj.x + obj.y}


会处理成:


function add(a, b){  const obj_x = a  const obj_y = b  return obj_x + obj_y}


四、总体流程



参考资料:

1.v8

2.编译型语言和解释型语言的区别

3.编译器与解释器的区别

4.js引擎能做到多小

5.深入理解JS引擎

6.V8是如何执行JavaScript代码的

7.JIT为什么能大幅度提升性能

8.JIT(just-in-time)即时编译



 作者简介


杨国旺

腾讯前端开发工程师

腾讯前端开发工程师,欢迎讨论前端问题。



 推荐阅读


图文解读:推荐算法架构——精排!

揭秘一致性Hash算法应用!

手把手教你从0开始实现C++协程!

阅见深我,读享生活,TVP读书分享会带你解锁新知!



浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报