Skip to content

⚠️ Important Notice

This post was last updated on: which was . Please pay attention to its timelines.

V8中的字节码

V8中的字节码是作为一个中间代码过度的,在执行一段JS代码之前,需要编译为字节码,然后再解释执行字节码或将字节码编译成二进制代码执行。

早期的V8是直接将JS代码转为机器码执行,编译很耗时,但执行时很快,所以为了防止每次重新进入要重新编译,会将机器码缓存起来。但是机器码占用内存太多,移动端不友好,所以后面进行重构,引入了字节码。

即并不直接转成机器码,而是先转为字节码,编译速度减少,缓存也是缓存的字节码,虽然后面执行时间会增长,但这也是权衡利弊之后的结果。

执行JS代码的大致流程如下:首先会调用V8中的解析器Parser将JS代码转为AST并生成对应作用域,然后将AST解释器处理;解释器中先转为字节码,然后解释执行,对于热点代码(会重复多次)会编译成二进制存储,可以加快执行效率。

NOTE

来自这篇文章V8的blog V8的执行有两个阶段:解析和编译 解析会经过扫描器Scanner和解析器Parser,编译会经过编译器 扫描器的工作就是解析代码,将代码转为token,对于暂时用不到的如用户交互的回调等会等到执行才扫描。 解析器的工作是将token转成AST 编译器的工作是将AST转成字节码,对于执行了很多次的字节码,还会转为机器码。

JIT(Just-In-Time): 即时编译。即上面编译器的作用。 Ignition是V8基于TurboFan实现的一个解释器 和 TurboFan是V8的优化编译器之一。都是JIT的组成部分。 Ignition主要工作是将AST转成字节码,执行过程会使用TurboFan进行优化编译,hotcode转为机器码;当优化不再有效,会执行deoptimization,回到字节码。

关于预解析PreParser 由于Lazy parsing的存在,对于某些函数是不会解析的,但是语法又无法保证,所以有预解析器来判断函数语法是否有效,并且编译生成外部函数需要的信息 等到执行时再完全编译成字节码。 因为JS中函数本质是对象,所以可以在函数中调用函数,形成闭包。但之前说了解析代码会有lazy parsing, 导致闭包的代码是扫描不到的。所以引入了预解析器来处理这部分。 我们都知道函数执行上下文在执行完成之后会被销毁,怎么保证闭包引用的变量不被销毁呢?V8的处理是将闭包的变量保存到了堆中,然后在执行闭包的时候,将堆中的变量复制到栈中,这样就可以保证变量不被销毁。

从上面看出,引入字节码可以带来以下的好处:

  • 编译成字节码比编译成机器码要更快,所以启动会更快;

  • 字节码比机器码内存要小,所以内存占用更少;

  • 字节码是平台无关的,可以降低跨平台的难度。

虽然执行机器码比执行字节码要快,但是权衡利弊之后还是选择引入字节码。

上一次更新: