V8是如何执行一段JavaScript代码的?
什么是V8?
V8是一个由Google开发的开源JavaScript引擎,目前用在Chrome浏览器和Node.js中,其核心功能是执行易于人类理解的JavaScript代码。
那么V8又是怎么执行JavaScript代码的呢?
其主要核心流程分为编译和执行两步。首先需要将JavaScript代码转换为低级中间代码或者机器能够理解的机器代码,然后再执行转换后的代码并输出执行结果。
所以对于JavaScript代码来说,V8就是它的整个世界,当V8执行JavaScript代码时,你并不需要担心现实中不同操作系统的差异,也不需要担心不同体系结构计算机的差异,你只需要按照虚拟机的规范写好代码就可以了。
高级代码为什么需要先编译再执行?
通过二进制的指令和CPU进行沟通。为了能够完成复杂的任务,工程师们为CPU提供了一大堆指令,来实现各种功能,我们就把这一大堆指令称为指令集(Instructions),也就是机器语言。
高级语言-汇编语言-机器语言
执行高级语言的两种基本方式:解释执行和编译执行。
V8是怎么执行JavaScript代码的?
V8并没有采用某种单一的技术,而是混合编译执行和解释执行这两种手段,我们把这种混合使用编译器和解释器的技术称为JIT(Just In Time)技术。
这是一种权衡策略,因为这两种方法都各自有各自的优缺点,解释执行的启动速度快,但是执行时的速度慢,而编译执行的启动速度慢,但是执行时的速度快。你可以参考下面完整的V8执行JavaScript的流程图:
源代码- AST+作用域-字节码-解释器
源代码- AST+作用域-字节码-机器代码
跟踪一段实际代码的执行流程
以一段简单的JavaScript代码示例,将它交给V8引擎,看处理执行过程是怎么样的?
代码如下所示:var test = 'GeekTime'
首先这段代码会被解析器结构化成AST
要查看V8中间生成的一些结构,可以使用V8提供的调试工具D8来查看,你可以将上面那段代
码保存到test.js的文件中,然后执行下面命令:d8 --print-ast test.js
执行结果:
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . VARIABLE (0x7ff0e3022298) (mode = VAR, assigned = true) "test"
. BLOCK NOCOMPLETIONS at -1
. . EXPRESSION STATEMENT at 11
. . . INIT at 11
. . . . VAR PROXY unallocated (0x7ff0e3022298) (mode = VAR, assigned = true) "test"
. . . . LITERAL "GeekTime"
解析之后,AST是个树状结构,直观地理解,你可以将其转换为一个图形树,如下图所示:d8 --print-scopes test.js
执行这段命令之后,D8会打印出如下内容:
Global scope:
global { // (0x7fd974022048) (0, 24)
// will be compiled
// 1 stack slots
// temporary vars:
TEMPORARY .result; // (0x7fd9740223c8) local[0]
// local vars:
VAR test; // (0x7fd974022298)
}
上面这行代码生成了一个全局作用域,我们可以看到test变量被添加进了这个全局作用域中。
生成了AST和作用域之后,就可以使用解释器生成字节码了,同样你可以使用D8来打印生成后的字节码,打印的命令如下所示:d8 --print-bytecode test.js
执行这段语句,最终打印出来的结果如下所示:
[generated bytecode for function: (0x2b510824fd55 <SharedFunctionInfo>)]
Parameter count 1
Register count 4
Frame size 32
0x2b510824fdd2 @ 0 : a7 StackCheck
0x2b510824fdd3 @ 1 : 12 00 LdaConstant [0]
0x2b510824fdd5 @ 3 : 26 fa Star r1
0x2b510824fdd7 @ 5 : 0b LdaZero
0x2b510824fdd8 @ 6 : 26 f9 Star r2
0x2b510824fdda @ 8 : 27 fe f8 Mov <closure>, r3
0x2b510824fddd @ 11 : 61 32 01 fa 03 CallRuntime [DeclareGlobals], r1-r3
0x2b510824fde2 @ 16 : 12 01 LdaConstant [1]
0x2b510824fde4 @ 18 : 15 02 02 StaGlobal [2], [2]
0x2b510824fde7 @ 21 : 0d LdaUndefined
0x2b510824fde8 @ 22 : ab Return
Constant pool (size = 3)
0x2b510824fd9d: [FixedArray] in OldSpace
- map: 0x2b51080404b1 <Map>
- length: 3
0: 0x2b510824fd7d <FixedArray[4]>
1: 0x2b510824fd1d <String[#8]: GeekTime>
2: 0x2b51081c8549 <String[#4]: test>
Handler Table (size = 0)
Source Position Table (size = 0)
生成字节码之后,解释器会解释执行这段字节码,如果重复执行了某段代码,监控器就会将其标记为热点代码,并提交给编译器优化执行,如果你想要查看那些代码被优化了,可以使用下面的命令:d8 --trace-opt test.js
如果要查看那些代码被反优化了,可以使用如下命令行来查看:pt --trace-deopt test.js
由于我们这段代码过于简单,没有触发V8的优化机制,在这里我们也就不展开介绍优化机制了,具体的流程。
此文章为5月Day9学习笔记,内容来源于极客时间《图解 Google V8》,日拱一卒,每天进步一点点💪💪
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。