Node.js 底层#4 - 谈谈 V8

2025-06-07

Node.js 底层#4 - 谈谈 V8

在我们之前的文章中,我们讨论了 JavaScript 和 JavaScript 引擎的最后一点。

现在我们已经深入 Node.js 的底层,事情开始变得混乱和复杂。我们开始讨论 JavaScript,这是我们所掌握的更高层次的概念,然后我们又讲了一些概念,例如:调用堆栈、事件循环、堆、队列等等……

问题是:这些东西实际上都不是 JS 实现的,它们都是引擎的一部分。所以 JavaScript 本质上是一种动态类型的解释型语言,我们在 JavaScript 中运行的所有内容都会传递给引擎,引擎会与其环境交互并生成机器运行程序所需的字节码。

而这款发动机就叫做V8。

什么是 V8

V8 是 Google 的开源高性能 JavaScript 和 WebAssembly 引擎。它用 C++ 编写,可在 Chrome 或类似 Chrome 的环境以及 Node.js 中使用。V8 拥有 ECMAScript 和 WebAssembly 的完整实现。但它不依赖于浏览器,实际上,V8 可以独立运行,也可以嵌入到任何 C++ 应用程序中。

概述

V8 最初的设计初衷是为了提升 Web 浏览器中 JavaScript 的执行性能——这也是 Chrome 浏览器在当时与其他浏览器相比速度差距巨大的原因。为了实现这一性能提升,V8 所做的不仅仅是解释 JavaScript 代码,它还会将这些代码转换为更高效的机器码。它通过实现所谓的JIT(Just In Time,即时编译器)编译器,在运行时将 JS 编译成机器码。

到目前为止,大多数引擎的工作方式实际上都相同,V8 与其他引擎最大的区别在于它根本不生成任何中间代码。它首次运行代码时,会使用名为 Ignition 的未优化编译器,直接将代码编译成应有的读取方式。之后,经过几次运行后,另一个编译器(JIT 编译器)会获取大量关于代码在大多数情况下实际行为的信息,并重新编译代码,使其根据当时的运行方式进行优化。这基本上就是对某些代码进行“JIT 编译”的含义。与 C++ 等使用 AoT (提前)compile编译的其他语言不同,这意味着我们先编译,生成可执行文件,然后运行它。Node中没有任务。

V8 还使用了许多不同的线程来提高速度:

  • 主线程负责获取、编译和执行 JS 代码
  • 另一个线程用于优化编译,因此主线程在前一个线程优化正在运行的代码时继续执行
  • 第三个线程仅用于分析,它告诉运行时哪些方法需要优化
  • 其他一些线程用于处理垃圾收集

抽象语法树

几乎所有语言的编译流程的第一步都是生成所谓的AST(抽象语法树)。抽象语法树是以抽象形式呈现给定源代码语法结构的树形表示,这意味着理论上它可以被翻译成任何其他语言。树中的每个节点都表示源代码中出现的一种语言结构。

让我们回顾一下我们的代码:

const fs = require('fs')
const path = require('path')
const filePath = path.resolve(`../myDir/myFile.md`)

// Parses the buffer into a string
function callback (data) {
  return data.toString()
}

// Transforms the function into a promise
const readFileAsync = (filePath) => {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) return reject(err)
      return resolve(callback(data))
    })
  })
}

(function start () {
  readFileAsync(filePath)
    .then()
    .catch(console.error)
})()
Enter fullscreen mode Exit fullscreen mode

这是我们代码中的一个 JSON 格式的示例 AST(其中的一部分),由名为esprimareadFile的工具生成

{
  "type": "Program", // The type of our AST
  "body": [ // The body of our program, an index per line
      {
          "type": "VariableDeclaration", // We start with a variable declaration
          "declarations": [
              {
                  "type": "VariableDeclarator",
                  "id": {
                      "type": "Identifier", // This variable is an identifier
                      "name": "fs" // called 'fs'
                  },
                  "init": { // We equal this variable to something
                      "type": "CallExpression", // This something is a call expression to a function
                      "callee": {
                          "type": "Identifier", // Which is an identifier
                          "name": "require" // called 'require'
                      },
                      "arguments": [ // And we pass some arguments to this function
                          {
                              "type": "Literal", // The first one of them is a literal type (a string, number or so...)
                              "value": "fs", // with the value: 'fs'
                              "raw": "'fs'"
                          }
                      ]
                  }
              }
          ],
          "kind": "const" // Lastly, we declare that our VariableDeclaration is of type const
      }
  ]
}
Enter fullscreen mode Exit fullscreen mode

因此,正如我们在 JSON 中看到的,我们有一个名为的打开键type,它表示我们的代码是Program,并且我们有它的bodybody键是一个对象数组,其中每个索引代表一行代码。我们的第一行代码是,const fs = require('fs')所以它是数组的第一个索引。在第一个对象中,我们有一个type键,表示我们正在做的是变量声明,以及这个特定变量的声明(因为我们可以做const a,b = 2,所以declarations键是一个数组,每个变量一个)fs。我们有一个type名为VariableDeclarator,它标识我们正在声明一个名为的新标识符fs

之后,我们初始化变量,也就是initkey,它表示从=符号开始的所有内容。keyinit是另一个对象,它定义了我们正在调用一个名为的函数require,并传递一个 value 的字面参数fs。所以基本上,这整个 JSON 定义了我们一行代码。

AST 是所有编译器的基础,因为它允许编译器将高层表示(代码)转换为低层表示(树),从而删除我们放入代码中的所有无用信息,例如注释。此外,AST 还允许我们(普通程序员)随意修改代码,这基本上就是智能感知或其他代码助手的功能:它会分析 AST,并根据您目前编写的内容,建议后续可以添加的代码。AST 还可以用来动态替换或修改代码,例如,我们只需查看中的键值,就可以将所有 替换letconstkindVariableDeclaration

如果 AST 使我们能够识别性能问题并分析代码,那么它对编译器也同样如此。这就是编译器的本质:分析、优化并生成可由机器运行的代码。

结论

这是我们关于 V8 及其工作原理的讨论的开始!我们将讨论字节码和其他许多有趣的内容!敬请期待下一章 :D

文章来源:https://dev.to/_staticvoid/node-js-under-the-hood-4-let-s-talk-about-v8-1eol
PREV
Node.js 底层原理 #7 - 全新 V8
NEXT
如何使用 TSX 在 Node.js 中原生运行 TypeScript