高级 JavaScript 系列 - 第一部分:幕后(JavaScript 引擎、ATS、隐藏类、垃圾回收)

2025-06-07

高级 JavaScript 系列 - 第一部分:幕后(JavaScript 引擎、ATS、隐藏类、垃圾回收)

介绍-

JavaScript 是一种单线程、同步编程语言。这意味着当脚本运行时,JS 引擎会逐行运行代码,从顶部开始向下执行。

幕后花絮-

流程图1
图片来源:Yair Cohen

1. JavaScript引擎

流程图2
图片来源:Yair Cohen

  • 每个 JavaScript 程序都需要特定的环境才能执行,因为我们的计算机和其他机器不理解 JavaScript 语法。
  • 它们只理解机器代码,因此每个环境都有一个引擎将这种 JS 人类可理解的语法转换为机器代码。
  • 有许多不同的引擎可用,其中最受欢迎的是 Google Chrome 的 V8 引擎、Firefox SpiderMonkey、Safari 的 JavaScriptCore 等。
  • ECMAScript是一种 JavaScript 标准,它通过检查所有不同引擎如何解释 JavaScript 语言来帮助确保 JS 网页的互操作性。

2. 解析器/语法解析器

流程图3
图片来源:Yair Cohen

  • 每个 JS 引擎都有一个解析器,它知道所有的 JS 语法规则并检查任何语法或语法错误。
  • 如果找到,它会发出错误,否则解析器会生成一个抽象语法树,然后将其传递以帮助代码执行。

3.抽象语法树(AST)

流程图4
图片来源:Yair Cohen

  • 它是 JS 代码的树状结构表示。
  • 创建 AST 的主要目的是帮助更好地理解代码并有助于更轻松地转换为机器代码。
  • 您可以在AST Explorer上看到 AST 的形成和表示方式

4. 解释器

流程图5
图片来源:Yair Cohen

  • 解释器获取 AST 并进行解析,然后将其转换为中间表示

中级代表-

  • 中间表示充当从 JS 等抽象语言到机器代码的翻译的中间步骤。
  • JS 引擎中最著名的中间表示是字节码红外线 鸣谢:Satyabrata Jena
需要中间代表(IR)-
  1. 与依赖于硬件的机器代码不同,IR 是通用的,因此允许更高的移动性和更容易的转换。
  2. 当代码处于 IR 中时,优化代码比优化机器代码更容易。

5.编译器

流程图6
图片来源:Yair Cohen

  • 编译器的主要目的是获取上一步收到的中间表示,执行优化,然后将其转换为机器代码。

解释器和编译器之间的区别

  • 解释器和编译器的不同之处在于,解释器会翻译代码并逐行运行,而编译器则会在执行之前将所有代码立即转换为机器代码。
  • 每种方法都有其优点和缺点;编译器速度快但复杂且难以启动,而解释器速度慢但更简单
  • 考虑到这一点,有三种方法可以将高级代码转换为机器代码并运行:
  1. 解释——这种技术使用解释器逐行执行代码并执行(效率不太高)。
  2. 提前编译 (AOT) - 需要编译器首先编译然后执行完整的代码。
  3. 即时编译 (JIT) — JIT 编译方法结合了 AOT 和解释策略,旨在通过执行动态编译来结合两者的优点,同时允许优化,从而显著加快编译过程。
  • 大多数 JS 引擎都使用 JIT 编译器,但并非所有引擎都使用。
  • 请参阅本文获得有关该主题的更完整的解释。

附加内容-

1.隐藏类

  • 众所周知,JavaScript 是一种动态编程语言。
  • 虽然这是 JavaScript 动态特性的一个优点,但它也有一个缺点。在内存中,JS 对象存储在所谓的哈希表中。与非动态编程语言中使用的连续缓冲区方法相比,使用哈希表检索对象属性的速度要慢得多。
  • V8 引擎提供的一种机制——隐藏类,给出了答案。隐藏类用于减少从对象中检索属性所需的时间。这是通过在外观相似的对象之间共享隐藏类来实现的。创建 JavaScript 对象时,会为其分配一个隐藏类。
  • 可以根据属性的类型轻松确定到达隐藏类的偏移长度,但这在 JavaScript 中是不可能的,因为 JavaScript 的属性类型在运行时可能会发生变化。
  • 隐藏类是在运行时附加的。
  • 当一个属性被引入到对象中时,就会发生“类转换”,其中先前的隐藏类会被包含新属性的新隐藏类所取代。我们来看一个例子来帮助你理解。

函数杯形蛋糕(糖霜,洒){
this.frosting = 糖霜;
this.sprinkles = 洒;
}

* We have a constructor function cupcake that takes as argument the frosting type and the sprinkles type and whenever this function is invoked; we get an object that is our new Cupcake!
* V8 creates a hidden class called Class0 when it sees our cupcake function is declared. When V8 notices that frosting has been added as a property on the cupcake on line 2, it changes class0 with the new frosting property and switches from class0 to a new hidden class called class1. The same happens when sprinkles are added to the cupcake and class transition occurs from class1 to class2.
* Checkout this [article](https://medium.com/swlh/writing-optimized-code-in-js-by-understanding-hidden-classes-3dd42862ad1d) for a more in-depth explanation on hidden classes.

### 2. Inline Caching
*  **Inline caching** relies upon the observation that repeated calls to the same method tend to occur on the same type of object. [2]
* V8 keeps a cache of the types of objects that have been **supplied as parameters** in recent method calls and uses that data to guess what type of object will be passed as a parameter in the future. 
* If **V8** can make a good guess about the type of object that will be provided to a method, it can skip the process of finding out how to access the object's properties and instead **rely on previously stored** information from lookups to the hidden class.
![Objects](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b485rs95wr3sn6lzhfap.png)
*Credits- [Yair Cohen](https://coralogix.com/blog/how-js-works-behind-the-scenes%E2%80%8A-%E2%80%8Athe-engine/)*

#### Relation between Hidden classes and Inline caching
* When a method on a specific object is called, the **V8 engine** must look up the hidden class of that object to calculate the offset for accessing a specific attribute. V8 skips the hidden class lookup after two successful calls to the same hidden class and simply adds the offset of the property to the object pointer itself. The V8 engine thinks that the **hidden class** hasn't changed for all subsequent calls to that method, and leaps directly into the memory address for a given field using offsets recorded from prior lookups, considerably **increasing execution performance**.
* The importance of objects of the same type **sharing hidden classes** is due to inline caching. V8 will not be able to use **inline caching** if you create two objects of the same type but with different hidden classes (as we did in the previous example). This is because, despite the fact that the two objects are of the same type, their corresponding hidden classes assign different offsets to their properties.
* JS being **dynamically typed**, occasionally the hidden class assumption about the object might be wrong. In that case V8 goes for the original call that is searching from the Hash Table that makes the data fetching slower.

#### Optimizations to take advantage of Hidden Classes and Inline Caching-
* Try to assign all properties of an object in its constructor.
* If still(for some reason), you are dynamically adding new properties to the objects, always instantiate them in the **SAME ORDER so that hidden classes may be shared** among them because then the V8 engine is able to predict them thus assigning the same hidden class to both the objects.
* Below is an example of a good and a bad practice for this use case-

##### Bad Practice-
Enter fullscreen mode Exit fullscreen mode

1 函数 Point(x,y) {
2 this.x = x;
3 this.y = y;
4 }
5
7 var obj1 = new Point(1,2);
8 var obj2 = new Point(3,4);
9
10 obj1.a = 5;
11 obj1.b = 10;
12
13 obj2.b = 10;
14 obj2.a = 5;

Up until line 9, obj1 and obj2 shared the same hidden class. However, since properties a and b were added in opposite orders, obj1 and obj2 end up with different hidden classes.

##### Good Practice-
Enter fullscreen mode Exit fullscreen mode

1 函数 Point(x,y) {
2 this.x = x;
3 this.y = y;
4 }
5
7 var obj1 = new Point(1,2);
8 var obj2 = new Point(3,4);
9
10 obj1.a = 5;
11 obj2.a = 5;
12
13 obj1.b = 10;
14 obj2.b = 10;


### 3. Garbage Collection

* JavaScript is a **Garbage collected language**.
* Meaning that if we allocate some memory inside a function, JavaScript will automatically deallocate that memory once the function finishes executing or is out of scope.
* But the issue of **[Memory Leak](https://en.wikipedia.org/wiki/Memory_leak)** still prevails in JS like in other languages. Thus it is important to ensure good memory management on our part.
* JS collects garbage with a **[mark and sweep](https://www.geeksforgeeks.org/mark-and-sweep-garbage-collection-algorithm/)** method.
![Mark and sweep](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qiq0w2a6uuhz8lrvoaae.png)
*Credits- [Andrei Neagoie](https://zerotomastery.io/cheatsheets/javascript-cheatsheet-the-advanced-concepts/?utm_source=udemy&utm_medium=coursecontent#call-stack-memory-heap)*
<hr/>
![Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i71muydk6hy8toeuj4rw.png)
*[Open code in JS Fiddle](https://jsfiddle.net/j7569dfh/)*

* In this example, a **memory leak** is created. By changing the value of `person`, we leave the previous value in the memory heap thus causing a leak.
* Best practice against memory leaks are to avoid global instantiation, instead we should instantiate only inside functions, where required.

<hr/>
## Connect with me-
- [GitHub](https://github.com/Pranav016)
- [LinkedIn](https://www.linkedin.com/in/pranav-mendiratta/)

<hr/>
## Appendix-

1. [**Advanced JavaScript Series - Part 1**: Behind the scenes (JavaScript Engine, ATS, Hidden Classes, Garbage Collection)](https://dev.to/pranav016/advanced-javascript-series-part-1-behind-the-scenes-javascript-engine-ats-hidden-classes-garbage-collection-3ajj)
1. [**Advanced JavaScript Series - Part 2**: Execution Context and Call Stack](https://dev.to/pranav016/advanced-javascript-series-part-1-execution-context-and-call-stack-l1o)
1. [**Advanced JavaScript Series - Part 3**: Weird JS behavior, Strict Mode and Hoisting, Temporal Dead Zone](https://dev.to/pranav016/advanced-javascript-series-part-3-weird-js-behavior-strict-mode-and-hoisting-26a3)
1. [**Advanced JavaScript Series - Part 4.1**: Global, Function and Block Scope, Lexical vs Dynamic Scoping](https://dev.to/pranav016/advanced-javascript-series-part-41-global-function-and-block-scope-lexical-vs-dynamic-scoping-20pg)
1. [**Advanced JavaScript Series - Part 4.2**: Scope Chains and their working, Lexical and Variable Environments](https://dev.to/pranav016/advanced-javascript-series-part-42-scope-chains-and-their-working-lexical-and-variable-environments-19d5)
1. [**Advanced JavaScript Series - Part 5**: IIFE & 'this' keyword in JS(tricky Eg.), call(), apply(), bind(), Currying(Functional Prog)](https://dev.to/pranav016/advanced-javascript-series-part-5-iife-this-keyword-in-jstricky-eg-call-apply-bind-curryingfunctional-prog-98c)
1. [**Advanced JavaScript Series - Part 6.1**: Everything in JS is an Object? Weird JS behaviors revealed, Primitive Non-Primitive Types](https://dev.to/pranav016/advanced-javascript-series-part-61-everything-in-js-is-an-object-primitive-non-primitive-types-1d8c)
1. [**Advanced JavaScript Series - Part 6.2**: Pass by Value & Pass by Reference, Shallow & Deep Copy, Type Coercion](https://dev.to/pranav016/advanced-javascript-series-part-62-pass-by-value-pass-by-reference-shallow-deep-copy-type-coercion-49f3)
1. [**Advanced JavaScript Series - Part 7**: First Class Citizens & Higher Order Functions](https://dev.to/pranav016/advanced-javascript-series-part-7-first-class-citizens-higher-order-functions-3cda)
1. [**Advanced JavaScript Series - Part 8**: The 2 Pillars~ Closures & Prototypal Inheritance](https://dev.to/pranav016/advanced-javascript-series-part-8-the-2-pillars-closures-prototypal-inheritance-4m5n)
1. [**Advanced JavaScript Series - Part 9**: Constructor Functions, Object Oriented, `new` keyword](https://dev.to/pranav016/advanced-javascript-series-part-9-constructor-functions-object-oriented-new-keyword-1gg0)
<hr/>

## References-
1. https://coralogix.com/blog/how-js-works-behind-the-scenes%E2%80%8A-%E2%80%8Athe-engine/
1. https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html
1. https://www.geeksforgeeks.org/difference-between-source-code-and-byte-code/
1. https://zerotomastery.io/cheatsheets/javascript-cheatsheet-the-advanced-concepts/?utm_source=udemy&utm_medium=coursecontent#call-stack-memory-heap
1. https://medium.com/swlh/writing-optimized-code-in-js-by-understanding-hidden-classes-3dd42862ad1d
Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/pranav016/advanced-javascript-series-part-1-behind-the-scenes-javascript-engine-ats-hidden-classes-garbage-collection-3ajj
PREV
常用Git命令汇总+Git使用难点场景解决方案
NEXT
如何让你的 Ubuntu 桌面运行得更快