一、什么是 V8
V8 是一个由 Google 开发的开源 JavaScript 引擎,目前用在 Chrome 浏览器和 Node.js 中,其核心功能是执行 JavaScript 代码。其主要核心流程分为编译和执行两步。首先需要将 JavaScript 代码转换为低级中间代码或者机器能够理解的机器代码,然后再执行转换后的代码并输出执行结果。
1. 虚拟机
可以把 V8 看成是一个虚构出来的计算机,也称为虚拟机,虚拟机通过模拟实际计算机的各种功能来实现代码的执行,如模拟实际计算机的 CPU、堆栈、寄存器等,虚拟机还具有它自己的一套指令系统。当 V8 执行 JavaScript 代码时,不需要担心现实中不同操作系统的差异,也不需要担心不同体系结构计算机的差异,只需要按照虚拟机的规范写好代码即可。
2. 高级代码为什么要先编译再执行
(1)机器语言
为了能够完成复杂的任务,工程师们为 CPU 提供了一大堆指令,来实现各种功能,我们就把这一大堆指令称为指令集(Instructions),也就是机器语言。
(2)汇编指令集
CPU 只能识别二进制的指令,但是对程序员来说,二进制代码难以阅读和记忆,于是我们又将二进制指令集转换为人类可以识别和记忆的符号,这就是汇编指令集。机器不能直接理解汇编语言。所以,你使用汇编编写了一段程序,还需要一个汇编编译器,其作用是将汇编代码编程成机器代码。
(3)高级语言
汇编语言对机器语言做了一层抽象,减少了理解机器语言的复杂度,但是汇编语言依然是复杂且繁琐的。这主要表现在以下两点
不同的 CPU 拥有不同的指令集
如果要使用机器语言或者汇编语言来实现一个功能,需要为每种架构的 CPU 编写特定的汇编代码。
在编写汇编代码时,我们还需要了解和处理器架构相关的硬件知识
比如你需要使用寄存器、内存、操作 CPU 等。大部分程序员在编写应用的时候,只想专心处理业务逻辑,并不想要过多地理会这些处理器架构相关的细节。因此我们需要一种屏蔽了计算机架构细节的语言,能适应多种不同 CPU 架构的语言,能专心处理业务逻辑的语言,于是就有了各种“高级语言”。
(4)高级语言执行方式
解释执行
需要先将输入的源代码通过解析器编译成中间代码,之后直接使用解释器解释执行中间代码,然后直接输出结果。
编译执行
也需要先将源代码转换为中间代码,然后编译器再将中间代码编译成机器代码。通常编译成的机器代码是以二进制文件形式存储的,需要执行这段程序的时候直接执行二进制文件。还可以使用虚拟机将编译后的机器代码保存在内存中,然后直接执行内存中的二进制代码。
二、V8 怎么执行 JavaScript
V8 没有采用某种单一的技术,而是混合编译执行和解释执行这两种手段,这种混合使用编译器和解释器的技术称为 JIT(Just In Time)技术。这是一种权衡策略,因为这两种方法都各自有各自的优缺点,解释执行的启动速度快,但是执行时的速度慢,而编译执行的启动速度慢,但是执行时的速度快。大致流程可参考下图:

1. 准备基础环境
图中左侧可以看出,在 V8 启动执行 JavaScript 之前,它还需要准备执行 JavaScript 时所需要的一些基础环境,这些基础环境包括了“堆空间”,“栈空间”,“全局执行上下文”,“全局作用域”,“消息循环系统”,“内置函数”等,这些内容都是在执行 JavaScript 过程中需要使用到的。
2. 接收 JavaScript 源代码并结构化
首先 V8 会接收到要执行的 JavaScript 源代码,不过这对 V8 来说只是一堆字符串,V8 并不能直接理解这段字符串的含义,它需要结构化这段字符串。
结构化是指信息经过分析后可分解成多个互相关联的组成部分,各组成部分间有明确的层次结构,方便使用和维护,并有一定的操作规范。
V8 源代码的结构化之后,就生成了抽象语法树 (AST),我们称为 AST,AST 是便于 V8 理解的结构。在生成 AST 的同时,V8 还会生成相关的作用域,作用域中存放相关变量。
3. 生成字节码
字节码是介于 AST 和机器代码的中间代码。但是与特定类型的机器代码无关,解释器可以直接解释执行字节码,或者通过编译器将其编译为二进制的机器代码再执行。
4. 解释执行
解释器按照顺序解释执行字节码,并输出执行结果。
5. 优化热点代码
V8 还存在一个监控解释器状态的模块。在解释执行字节码的过程中,如果发现了某一段代码会被重复多次执行,就会将这段代码标记为热点代码。
某段代码被标记为热点代码后,V8 就会将这段字节码丢给优化编译器,优化编译器会在后台将字节码编译为二进制代码,然后再对编译后的二进制代码执行优化操作,优化后的二进制机器代码的执行效率会得到大幅提升。如果下面再执行到这段代码时,V8 会优先选择优化之后的二进制代码,这样代码的执行速度就会大幅提升。
6. 反优化
JavaScript 是一种非常灵活的动态语言,对象的结构和属性是可以在运行时任意修改的,而经过优化编译器优化过的代码只能针对某种固定的结构,一旦在执行过程中,对象的结构被动态修改了,那么优化之后的代码势必会变成无效的代码,这时候优化编译器就需要执行反优化操作,经过反优化的代码,下次执行时就会回退到解释器解释执行。
三、安装 v8
1. 通过官方文档的方式安装
V8 是 Google 开源的 JavaScript 引擎,并且在 github 也维护有镜像。V8 官网提供了从源码编译安装 V8 的方式 从源码编译 V8
Google 的资源需要科学上网,且我的电脑上 cmder 里面配置 git 的 http.proxy 也没有成功,始终无法走 vpn 的代理流量,因此这个方式失败了
2. 通过 jsvu 安装
jsvu 是一个 npm 包。jsvu 全称 JavaScript (engine) Version Updater。是一个用于直接在机器上安装各个版本 JavaScript 引擎的工具包。使用它可以不需要编译源码,直接安装。
(1)安装 jsvu
npm i jsvu -g
复制代码
(2)执行 jsvu
# 第一次执行,可以根据交互式命令行选择需要安装的平台和引擎
jsvu
# 选择完之后,jsvu 会记住这次选择,下次再执行时还是会引用上一次的配置
# -h 选项用于查看帮助信息
jsvu -h
� jsvu v1.13.2 — the JavaScript engine Version Updater �
[<engine>@<version>]
[--os={mac64,mac64arm,linux32,linux64,win32,win64,default}]
[--engines={chakra,graaljs,hermes,javascriptcore,quickjs,spidermonkey,v8,v8-debug,xs},…]
Complete documentation is online:
https://github.com/GoogleChromeLabs/jsvu#readme
# 因此,安装指定版本的引擎可以参考下面的命令
jsvu --os=win64 --engines=v8,v8-debug
复制代码
(3)查看 v8,v8-debug 帮助信息
安装好 v8,v8-debug 之后,切换到 C:\users%USERPROFILE%.jsvu 目录下,能看到安装好的 cmd 文件。查看帮助文档时,由于这两个引擎帮助文档过长,可以将输出重定向到文件,再使用文本编辑器打开来查看,命令示例如下:
touch v8-help.txt
v8 --help >> v8-help.txt
touch v8-debug-help.txt
v8-debug --help >> v8-debug-help.txt
复制代码
这样生成两份帮助文档,就能结合帮助文档来使用了。
(4)执行 v8,v8-debug
1)直接执行代码
在命令行中直接执行 JavaScript 代码,需要添加 -e 选项,并且代码被双引号包裹。
v8 -e "console.log(Date.now())"
1622362516291
复制代码
2)直接执行 JavaScript 文件
# 当前目录下新建一个 index.js 文件,并写入
# console.log(Date.now())
v8 index.js
1622362597761
# 必须要写完整的后缀名,否则报错
v8 index
Error reading 'index'
复制代码
(5)环境变量
按常理来说,将 C:\users%USERPROFILE%.jsvu 这个目录配置到环境变量里面,就能在任意目录执行。但事实上配置并不生效。且在命令行中输入 PATH 命令也没有找到这个地址。这一点需要继续考察。
四、跟踪代码执行
- 查看 AST
结合上述 JavaScript 执行流程,在准备好 V8 环境,接收到代码后,第一步就是“解释”。即解释器生成 AST 和作用域。
# jsvu 安装 v8-debug,切换到执行目录
v8-debug -e --print-ast "var test=100"
[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . VARIABLE (00000287FDC61870) (mode = VAR, assigned = true) "test"
. BLOCK NOCOMPLETIONS at -1
. . EXPRESSION STATEMENT at 9
. . . INIT at 9
. . . . VAR PROXY unallocated (00000287FDC61870) (mode = VAR, assigned = true) "test"
. . . . LITERAL 100
--print-ast 参数只能用于 v8-debug,v8 是没有的
复制代码

2. 查看作用域
# jsvu 安装 v8-debug,切换到执行目录
v8-debug -e --print-scopes "var test=100"
Global scope:
global { // (00000276E4C67CF8) (0, 12)
// will be compiled
// 1 stack slots
// temporary vars:
TEMPORARY .result; // (00000276E4C68030) local[0]
// local vars:
VAR test; // (00000276E4C67F40)
}
复制代码
上面这行代码生成了一个全局作用域,可以看到 test 变量被添加进了这个全局作用域中。
- 查看生成的字节码
有了 AST 和作用域后,就要根据二者生成字节码。解释器既要生成字节码,又要执行字节码。
# jsvu 安装 v8-debug,切换到执行目录
v8-debug -e --print-bytecode "var test=100"
[generated bytecode for function: (0x028b08292abd <SharedFunctionInfo>)]
Bytecode length: 18
Parameter count 1
Register count 3
Frame size 24
OSR nesting level: 0
Bytecode Age: 0
0000028B08292B4A @ 0 : 13 00 LdaConstant [0]
0000028B08292B4C @ 2 : c2 Star1
0000028B08292B4D @ 3 : 19 fe f8 Mov <closure>, r2
0000028B08292B50 @ 6 : 64 4f 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0000028B08292B55 @ 11 : 0d 64 LdaSmi [100]
0000028B08292B57 @ 13 : 23 01 00 StaGlobal [1], [0]
0000028B08292B5A @ 16 : 0e LdaUndefined
0000028B08292B5B @ 17 : a8 Return
Constant pool (size = 2)
0000028B08292B19: [FixedArray] in OldSpace
- map: 0x028b08002205 <Map>
- length: 2
0: 0x028b08292b05 <FixedArray[1]>
1: 0x028b0820bfb9 <String[4]: #test>
Handler Table (size = 0)
Source Position Table (size = 0)
复制代码
- 查看优化与反优化
生成字节码之后,解释器会解释执行这段字节码,如果重复执行了某段代码,监控器就会将其标记为热点代码,并提交给编译器优化执行,如果你想要查看那些代码被优化了,可以使用下面的命令:
v8-debug -e --trace-opt "var test=100"
复制代码
如果要查看那些代码被反优化了,可以使用如下命令行来查看
v8-debug -e --trace-deopt "var test=100"
复制代码






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)