JavaScript 浏览器的V8引擎(JavaScript的运行原理)

200 阅读6分钟

JavaScript 浏览器的V8引擎(JavaScript的运行原理)

V8引擎推荐阅读

JavaScript V8引擎介绍

V8 is the name of the JavaScript engine that powers Google Chrome. It's the thing that takes our JavaScript and executes it while browsing with Chrome.

V8 is the JavaScript engine i.e. it parses and executes JavaScript code. The DOM, and the other Web Platform APIs (they all makeup runtime environment) are provided by the browser.

The cool thing is that the JavaScript engine is independent of the browser in which it's hosted. This key feature enabled the rise of Node.js. V8 was chosen to be the engine that powered Node.js back in 2009, and as the popularity of Node.js exploded, V8 became the engine that now powers an incredible amount of server-side code written in JavaScript.

The Node.js ecosystem is huge and thanks to V8 which also powers desktop apps, with projects like Electron.

通过上面的描述,我们的V8引擎就是用来实现的对JavaScript 实现的解析以及运行的一种引擎模式

同时这个也是来自于我们的 Google,,这个也是为什么 Google 当前可以实现脱颖而出的原因之一

就是因为 Google 浏览器就是使用的是 V8 引擎

同时在 V8 引擎的存在之下,我们的 JavaScript 还可以实现进行服务端的开发 NodeJs

JavaScript JavaScript代码时如何实现运行的呐???

浏览器内核的组成是由两个部分组成的,接下来的就以 Webkit 为例子来实现解释:

  • WebCore: 负责进行HTML解析生成DOM Tree,解析CSS生成CSSOM,两者共同结合生成 Render Tree,引入布局layout等等
  • JavaScriptCore: 就是实现的是解析并且执行 JavaScript 代码

image-20241107182211195.png

除了浏览器内核为我们提供的 JavaScriptCore 之外,Google 浏览器中还存在一个用来实现 JavaScript 代码的解析以及运行的引擎

这个就是我们的 V8引擎

JavaScript V8引擎的执行原理

  • V8 使用的是 **C++ **进行的编写的一种 Google 开源的高性能的 JavaScript 和 WebAssembly 引擎,用于 Chrome 和 NodeJS 中
  • V8 可以实现独立的运行,同时也是可以实现嵌入到任何的 C++ 应用程序中

image-20241107183050270.png

上面的代码就是我们的 Java代码的解析和执行流程分析图

JavaScript V8引擎架构

V8 引擎的书写时包含了很多的模块的代码,这个分了不同的模块实现了对我们的 JavaScript 进行解析

V8引擎的解析 parse 模块

Parse 模块。这一步会将我们的 JavaScript 代码解析沾化为 AST (抽象语法树),这一步的实现是因为我们的 JavaScript 代码

并不能被解析器直接识别,所以说就需要进行解析转换

  • 如果说我们的 JavaScript 代码中的函数没有实现调用,那么就不会被添加到 AST 抽象语法树中去的
  • Parse 模块实现解析的文档解析: 官网阅读

image-20241107185915281.png

v8引擎解析器 Ignition 模块

lgnition 是一个解析器,实现的是将我们抽象语法树(AST)转换成ByteCode(字节码)

  • 在这个过程中同时会收集一些数据信息来让 TurboFan 来进行优化的操作(比如函数的类型信息)
  • 所以说我们的函数具有参数的话,可能因为传入的参数的数据类型的不同,导致最终的由 TurboFan 来进行解析的结果不同
  • 如何函数只是仅仅调用一次的话,lgnition 会直接解析执行 ByteCode
  • ignition的v8 官网阅读

One of the issues with this approach (in addition to architectural complexity) is that the JITed machine code can consume a significant amount of memory, even if the code is only executed once. In order to mitigate this overhead, the V8 team has built a new JavaScript interpreter, called Ignition, which can replace V8’s baseline compiler, executing code with less memory overhead and paving the way for a simpler script execution pipeline.

With Ignition, V8 compiles JavaScript functions to a concise bytecode, which is between 50% to 25% the size of the equivalent baseline machine code. This bytecode is then executed by a high-performance interpreter which yields execution speeds on real-world websites close to those of code generated by V8’s existing baseline compiler.

image-20241107190531001.png

V8引擎 TurboFan 优化模块

这个是一个编译器,可以实现的是将字节码(ByteCode) 编译为我们的 GPU 可以实现直接执行的机器码

  • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过 TurboFan 转换成优化的机器码,来提高代码的执行性能

  • 但是我们的机器码实际上的运行还是转换为的字节码(ByteCode

    • 这个的原理是因为我们的后续执行函数的时候,可能导致参数类型的变化,之前进行优化的机械码不能够实现对当前的
    • 数据类型进行解析执行,就会逆向的转换成字节码重新执行
  • TurboFan的V8官网阅读

image-20241107191950972.png

V8引擎总的解析流程图

image-20241107185915281.png

Blink 实现获取我们的源码

然后通过转化,转化为流 stream

然后进行的就是我们的词法分析(lexical analysis)

  • 将字符编码转换为token序列化的过程
  • token 就是标记的意思,tokenization 简写
  • 词法分析器也就是我们的 扫描器(scanner)
  • 这个步骤进行解析是通过的我们的JavaScript中代码根据不同的关键字进行解析的

随后就是语法分析(syntactic analysis 【parsing】)

  • 语法分析器就是我们的 parser

image-20241107193522837.png

上面的解析图的设计就是:

  1. 首先我们的浏览器实现获取我们的源码

  2. 然后通过解析模块解析成了抽象语法树

    1. 但是实际上的话,我们的抽象语法树内部实现了三步骤
    2. 第一个将源码解析成我们的 stream 流
    3. 然后通过词法解析器,词法解析器就实现通过标记化 token 来实现标记化
    4. 最后将这个词法解析的内容通过语法解析,生成语法解析内容
    5. 这个生成的语法解析树,就是最终的AST 抽象语法树
  3. 然后将我们的抽象语法树交给了 ignition 解析器进行解析流程

    1. 但是这里会通过前面的标记化来实现对函数的不同的解析的

    2. 如果是是没有被标记为 热点函数,那么就是直接转化为字节码实现执行

    3. 如果说函数被标记为了 热点函数,那么最后实现的就是将其转化为机械码,在转化为字节码实现执行函数

      1. 为什么会出现这一步耶???
      2. 就是因为我们函数中的参数类型的转变,最后参数数据类型的不同
      3. 是会导致以往编译的机械码无法满足于当下的代码执行的,这个时候就需要逆向解析为字节码,然后再来机械码,然后再运行函数