Skip to content

V8如何执行JavaScript代码的

作者:Annan
更新于:1 分钟前
字数统计:1.9k 字
阅读时长:6 分钟
阅读量:
V8 Blog
https://v8.dev/docs

什么是V8

V8是一个由 Google 开发的开源 JavaScript 引擎,目前用在 Chrome 浏览器和 Node.js 中,其核心功能是执行易于人类理解的 JavaScript 代码。

img

V8是如何执行JavaScript代码的

img

核心步骤分为编译和执行两步,首先需要将JavaScript代码转换为低级中间代码或者机器能理解的机器码,执行转换后的代码并输出结果。

可以将V8看成是一个虚拟机,通过模拟计算机的各种功能来实现代码的执行,比如模拟计算机的CPU、堆栈、寄存器等。虚拟机还具有它自己的一套指令系统。

高级代码为什么需要先编译后执行?

CPU只能执行机器码,CPU可以被认为是一个非常小的运算机器,我们可以给机器发送1000100111011000的二进制指令。当执行到这条指令时,就会执行一个操作。

为了完成复杂任务,CPU存在一大堆指令,这一大堆指令就叫做指令集。指令集都是二进制代码,对于CPU来说它可以认识,但是对于程序员来说二进制难以阅读和记忆,于是在指令集基础上,有了会变指令集。可以参考如下

javascript
1000100111011000 机器指令
mov ax,bx 汇编指令

但是CPU并不能直接识别汇编语言,需要一个翻译官(汇编编译器)将汇编语言翻译为机器可以识别的二进制

img

虽然汇编已经将二进制代码进行了一次抽象,但是想要实现一个功能还需要大量汇编语言。

由于不同的CPU有不同的指令集,要使用机器语言或者汇编语言实现一个功能,需要在每种CPU下编写特定的汇编代码。

img

而且在编写汇编代码时,还需要了解和处理器、架构相关的硬件知识,比如需要操作寄存器、内存、操作CPU等。大部分程序员在编写应用时指向专心处理业务逻辑,不想过多理会处理器架构相关细节。

因此高级语言应运而生了。如C、C++、Java、C#、Python、JavaScript 等。可以适应不同CPU架构的语言,屏蔽了计算机架构细节,可以专心处理业务逻辑。

与汇编语言一样,处理器无法识别高级语言编写的代码,通常有两种方式来执行这些代码。

第一种是解释执行,需要先将输入的源代码通过解析器编译成中间代码,之后直接使用解释器解释执行中间代码,然后直接输出结果。

img

第二种是编译执行。采用这种方式时,也需要先将源代码转换为中间代码,然后我们的编译器再将中间代码编译成机器代码。通常编译成的机器代码是以二进制文件形式存储的,需要执行这段程序的时候直接执行二进制文件就可以了。还可以使用虚拟机将编译后的机器代码保存在内存中,然后直接执行内存中的二进制代码。

img

以上是计算机执行高级语言的两种基本的方式:解释执行和编译执行。但是不同语言,这个过程会有差异。

比如要执行 C 语言编写的代码,你需要将其编译为二进制代码的文件,然后再直接执行二进制代码。而对于像 Java 语言、JavaScript 语言等,则需要不同虚拟机,模拟计算机的这个编译执行流程。执行 Java 语言,需要经过 Java 虚拟机的转换,执行 JavaScript 需要经过 JavaScript 虚拟机的转换。

而对于JavaScript而言,虚拟机也是多个的,苹果用的JavaScriptCore,Firefox 使用了 TraceMonkey 虚拟机,而 Chrome 则使用了 V8 虚拟机。

V8是如何执行JavaScript代码的?

V8作为JavaScript虚拟机的一种,是如何执行JavaScript的呢? V8并没有采用解释或编译的其中一种手段,而是将两者混合起来,被称为JIT(Just In Time)技术。

这是一种权衡策略,解释和编译各有优缺点,解释启动快,但执行慢。编译启动慢,但是执行快。下面是V8执行JavaScript的流程图。

img

可以看左图部分,在V8启动执行JavaScript之前,它需要准备一些基础环境,这些基础环境包括了堆空间、栈空间、全局执行上下文、全局作用域、消息循环系统、内置函数等。比如:

  • JavaScript全局上下文包含了执行过程中全局信息,比如一些内置函数,全局变量等信息。
  • 执行过程中的数据都需要存放在内存中,全局作用域包含一些全局变量。
  • V8用的是堆和栈的内存管理模式,因此V8需要初始化内存中的堆和栈结构。
  • 还需要初始化消息循环系统,这是让V8系统动起来的关键,消息循环系统包含消息驱动器和消息队列,就像V8的大脑,不断接受消息并决策如何处理消息(比如请求、setTimeout、事件之类的)

在基础环境准备好后,V8就可以开始执行JavaScript代码了。

首先V8会接收到需要执行的JavaScript源码,但是对于V8而言这些代码只是一堆字符串,想要理解这些字符串就需要结构化这段字符串,通过分析(词法分析(tokenize)->语法分析(parse))分解为AST。AST是V8可以理解的结构。

生成AST的同时,V8还会生成相关作用域(执行上下文),在作用域中存放相关变量。

在有AST和作用域后,解释器就可以生成字节码了,字节码是介于AST和机器代码的中间代码。

img

解释器可以直接逐行解释执行字节码,或者编译器将其编译为二进制的机器码执行。

在解释器下面有个机器人,这是用于监控解释器执行状态的模块,在解释器执行字节码时,如果发现某段代码被多次重复执行,这段代码就会被标记为热点代码。

当某段代码被标记为热点代码后,V8就会将这段字节码丢给编译器,编译器会将这段字节码编译为二进制代码,同时对编译后的二进制代码进行优化操作,使得这段二进制机器码执行效率大幅提升。如果再执行到这段热点代码时,V8会优先执行优化后的二进制机器码,这样整体的执行效率就会大幅提升。

Contributors

Annan