前言
上一篇文章我们提到了浏览器的渲染过程,HTML、CSS以及JS的解析,其中JS解析依赖于浏览器的JS引擎。因为JavaScript实际是一种高级语言,这种语言并不是机器语言。但是,计算机的CPU只能理解特定的机器语言,它不理解JavaScript语言,所以在计算机上执行JavaScript代码之前,必须将其转换为机器语言。
简单来说JavaScript引擎是一个解释和执行JavaScript代码的程序,负责将JavaScript代码转换成可执行的机器代码。 每个浏览器的JS引擎都是不同的,此篇文章我们着重讲一下谷歌浏览器的V8引擎。
V8引擎定义及特性
-
V8 是 Google 开发的一个高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写的开源项目。它用于 Chrome 浏览器、Node.js 等平台。
-
兼容性:V8 支持
ECMAScript
和WebAssembly
,并能够在多种操作系统上运行,如 Windows、macOS、和基于 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统。 -
V8 可以独立运行(虽然很少这么做),更常见的是它被嵌入到 C++ 应用程序中(例如 Node.js)。
-
主要目标:V8 的主要目标是提高 JavaScript 的性能和执行速度。
V8处理流程
-
源代码输入 。浏览器内核接收到js源代码后会将其传递给js引擎,在这也就是v8引擎。
-
解析 。通过Scanner(扫描器)进行词法分析Scanner转化结果就是一系列的词法单元(tokens),如关键字、标识符、操作符、字面量等。接下来,这些词法单元会被组织成抽象语法树(AST),该树表示代码的结构和逻辑(变量提升就是在此阶段处理的)。
-
初始编译。V8 的初始阶段使用名为
Ignition
的解释器对生成的 AST 进行字节码
编译。字节码是一种中间表示,介于源代码和机器码之间,它是跨平台的,并不直接依赖于具体的某一硬件。V8会直接解释执行这些字节码。 -
性能监控与优化。在字节码执行期间,V8会持续监控代码的执行情况,特别是那些频繁执行的代码。通过收集类型信息和性能数据,V8 能够识别哪些代码段可以进行优化。这一阶段的优化策略包括:
-
内联缓存(Inline Caches) :V8 会记录函数调用的类型信息,将频繁调用的小函数直接嵌入到调用方,减少函数调用的开销。
-
优化编译器(TurboFan) :V8 中有一个 TurboFan 优化编译器,它监控运行时性能,并根据收集到的类型信息,对频繁执行的代码进行优化,将其编译为高效的机器码。
-
-
优化代码生成。一旦 V8 检测到某些函数或代码段执行频繁,TurboFan 会将这些代码段编译为更高效的
机器码
,替代之前的字节码执行,进一步提升执行效率 -
回退。在某些情况下,如果代码的执行环境发生变化,例如变量类型发生变化不再符合优化编译时的假设,V8 可能会将优化的机器码回退为字节码执行,这种情况被称为
“反优化(Deoptimization)”
。这种机制解释了为什么在 TypeScript 项目中,确定的类型信息可以减少反优化的概率,从而提高执行效率。 -
执行。V8 解释并执行字节码/机器码,最终完成js代码的运行
词法分析
这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元/token
(词法单元是组成代码的最小单位,比如关键字、标识符、运算符、数字、字符串等)。
词法分析过程
-
输入: 接收整个源代码的文本。
-
分解: 将源代码按照字符顺序通过
Scanner
进行逐步扫描,并将其分解为一个个词法单元/token
。 -
输出: 输出这些词法单元作为后续语法分析的输入
例如,程序let a = 2;
可以分解为以下token:
[ { "type": "Keyword", "value": "let" }, { "type": "Identifier", "value": "x" }, { "type": "Operator", "value": "=" }, { "type": "Literal", "value": "10" }, { "type": "Punctuation", "value": ";" } ]
-
type
表示该词法单元的类型,例如Keyword
、Identifier
、Literal
等。 -
value
表示该词法单元在代码中实际的文本值,例如"let"
、"x"
、"="
等。
在大多数情况下,空白字符(如空格、换行符、制表符)不会被转换为独立的词法单元,除非它们在特定上下文中有语义意义(例如模板字符串中的空格)此外,除了 type
和 value
,在实际使用中,词法单元可能包含更多的字段,如start
和 end
记录词法单元在源代码中的开始和结束位置;line
和 column
字段记录词法单元在源代码中的行号和列号等。
语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为 “抽象语法树”(Abstract Syntax Tree,AST) 。
语法分析过程
-
输入: 接收由词法分析器生成的一系列词法单元(Tokens)。
-
预解析(Pre-parsing): 这是语法分析的一个优化阶段,并不是所有的js代码在一开始都会被执行,那么如果对所有的js代码进行解析,必然会影响网页的运行效率。预解析会跳过暂时不需要解析的代码,以减少加载开销。
-
解析(Parsing): 根据 JavaScript 的语法规则,生成AST.
-
输出: 生成的AST将作为后续编译、优化或解释执行的基础。
例如代码let a = 2;
的AST表示如下:
{
"type": "VariableDeclaration",
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal", // 或者 "NumericLiteral" 取决于解析器
"value": 2,
"raw": "2"
}
}
]
}
大家也可以使用AST Explorer工具来生成和查看js代码的AST。
V8的核心组件
Ignitio解释器
负责将 JavaScript 源代码经过解析生成的 AST 转换为字节码,并快速启动代码的执行
TurboFan优化编译器
它基于收集到的运行时信息,针对性能关键的代码路径进行优化编译,将字节码编译为高效的机器码。
我们上面提到计算机CPU 只能理解特定的机器语言,不能直接理解其他语言。机器码是直接由CPU执行的代码,可以直接在硬件上运行并没有额外的解释开销,而字节码是一种中间表示形式,需要由解释器(如 V8 的 Ignition)逐条解释和执行,这些额外的解释开销会降低代码的执行速度。另外机器码可以进行深度的编译器优化,针对特定硬件架构生成高效代码。
所以机器码效率要比字节码高。
Inline Caches内联缓存
通过记录函数调用的类型信息,内联缓存优化了函数调用,跳过了类型检查,加速了后续执行。
Garbage Collector垃圾回收
V8 引擎内置了高效的垃圾回收器(GC),自动管理内存分配和回收,避免内存泄漏。之后会开篇文章单独讲垃圾回收机制,在此不做过多讲解
好了以上就是此篇文章的相关内容了,大家如果觉得有所帮助可以给个赞。有不同观点也欢迎评论指出
撒花🎉🎉🎉