Node.js 底层 #5 - 隐藏类和变量分配

2025-06-10

Node.js 底层 #5 - 隐藏类和变量分配

(封面照片由 Jose Gabriel Ortega Castro 在 Unsplash 上拍摄)

在本系列的上一篇中,我们讨论了抽象语法树以及 V8 如何编译代码。V8 在处理 JavaScript 时的另一个很酷的功能是,它使静态类型语言(例如 C++)能够运行动态类型代码(例如 JS)。动态类型最简单的例子之一就是对象声明:

const myObj = {}
console.log(myObj) // {}

myObj.x = 1
console.log(myObj) // { x: 1 }

myObj.y = 2 // Dynamically changing the type
console.log(myObj) // { x: 1, y: 2 }
Enter fullscreen mode Exit fullscreen mode

由于 JavaScript 是一门动态语言,对象的属性可以动态添加和删除——就像我们之前做的那样。这些操作需要动态查找来解析属性在内存中的位置,以便返回相应的值。动态查找对处理器来说是一项高成本的操作。那么 V8 是如何处理这个问题的,从而使 JS 如此之快呢?答案是隐藏类。这也是 V8 著名的优化技巧之一。

我们稍后会讨论其他编译器优化技术

通常,当我们使用静态类型语言时,我们可以轻松确定属性在内存中的位置,因为所有对象和变量都由您定义为其类型的固定对象布局决定,并且新属性无法在运行时添加,这使得编译器很容易在内存中找到这些属性的值(或指针),因为它们可以存储为一个连续的缓冲区,并且每个对象之间都有固定的偏移量。并且这个偏移量可以通过对象类型轻松确定,因为所有类型都有固定的内存值。V8 利用这些固定布局对象的概念来使用隐藏类的方法。让我们看看它是如何工作的:

对于每个对象类型,V8 都会创建一个隐藏类,因此我们的第一个声明const myObj = {}将创建一个如下类:

现在,当我们向中添加一个新键时myObj,V8 会基于 C0(复制它)创建一个名为 C1 的新隐藏类,并将更新 C0 以向 C1 添加转换:

现在,作为我们添加的最后一条语句y,它执行与以前完全相同的步骤:基于 C1 创建一个新类 C2,向 C1 添加一个指向 C2 的新转换:

这个小技巧使 V8 能够将隐藏类重用于新对象。如果我们创建一个像这样的新对象{},则不会创建新类,相反,V8 会将新对象指向 C0。当我们添加新属性x和 时y,新对象将指向类 C1 和 C2,并将值写入这些类指定的偏移量。这个概念使编译器在访问属性时可以绕过字典查找。由于它已经知道对象指向哪个类以及该属性的偏移量在哪里,因此它可以直接访问那里。这也使得 V8 能够使用基于类的优化和内联缓存——我们稍后会看到。

然而,隐藏类极其不稳定,它们对于特定类型的对象而言是唯一的。因此,如果我们交换属性的顺序,yx不是相反,V8 就必须创建新的隐藏类,因为 C1 仅在位置 0 处具有 x 的偏移量,而 C2 仅在第一个位置具有 y 的偏移量。

但请记住这是在 C++ 中完成的,因为JavaScript 是一种基于原型的语言,因此它没有类。

结论

这只是对 V8 如何处理 JavaScript 内部结构的简要解释。理解内部变量分配和内部对象创建有助于我们编写更好、更高效的代码。

鏂囩珷鏉ユ簮锛�https://dev.to/_staticvoid/node-js-under-the-hood-5-hidden-classes-variable-allocations-1244
PREV
使用 Node.Js、ExpressJs、MongoDB 和 VueJs 构建 Todo 应用程序 – 第 2 部分
NEXT
Node.js 由 Baixo dos Panos #2 - Entendendo JavaScript