JS引擎

作者: shaokang 时间: July 10, 2023字数:3906

JavaScript 引擎

JavaScript 引擎是一种解析和执行 JavaScript 代码的程序或解释器。它可以在浏览器中或在 Node.js 服务器上运行。JavaScript 引擎能够将人类可读的 JavaScript 源代码转化为机器可以理解的低级机器代码或字节码。

JavaScript 引擎包括以下几种:

  • V8:开源,由 Google 开发,用于 Chrome 和 Node.js。
  • SpiderMonkey:第一个 JavaScript 引擎,由 Mozilla 开发,用于 Firefox 浏览器。
  • JavaScriptCore:开源,由苹果开发,用于 Safari 浏览器。
  • Chakra(JScript9):由 Microsoft 开发,用于 Internet Explorer。
  • Tamarin:由 Adobe 开发,用于执行 ActionScript。
  • QuickJS:小型的 JavaScript 引擎,由 Fabrice Bellard 和 Charlie Gordon 开发,可以将 JavaScript 源码转换为 C 语言代码,然后再使用系统编译器(gcc 或者 clang)生成可执行文件。
  • JerryScript:由三星开发的一款轻量级 JavaScript 引擎,用于嵌入式设备,如物联网设备和嵌入式操作系统。它可以在 RAM 小于 64KB 和 Flash 小于 200KB 的设备上运行。
  • Hermes:由 Facebook 开发,针对移动端 React Native 应用的 JavaScript 引擎。它旨在提高移动客户端应用 App 的性能,尤其是针对 React Native 应用启动性能的问题。

JerryScript 的设计目的是为了使 JavaScript 开发者能够更好地构建物联网应用。通过在嵌入式设备上运行 JavaScript,开发者可以利用 JavaScript 的灵活性和动态性,但不需要牺牲嵌入式设备的性能和资源效率。

JerryScript 提供了基本的 JavaScript 功能,包括变量、函数、控制结构、错误处理和垃圾回收等。它还支持 ECMAScript 规范的一部分,以便开发者可以使用熟悉的 JavaScript 语法和语义来编写嵌入式应用程序。

总的来说,JerryScript 是一个适用于嵌入式设备的轻量级 JavaScript 引擎,旨在帮助开发者在资源受限的环境中构建高效的物联网应用程序。

这些引擎的主要工作是解析(parsing)和执行(execution)JavaScript 代码。解析是将源代码转化为抽象语法树(AST)的过程,而执行则是将 AST 转化为字节码或机器码,并在硬件上运行的过程。此外,现代的 JavaScript 引擎如 V8 还包括了一些优化步骤,如即时编译(JIT)、垃圾回收等。

V8 引擎

在为数不多 JavaScript 引擎中,V8 无疑是最流行的,Chrome 与 Node.js 都使用了 V8 引擎,Chrome 的市场占有率高达 60%,而 Node.js 是 JS 后端编程的事实标准。国内的众多浏览器,其实都是基于 Chromium 浏览器开发,而 Chromium 相当于开源版本的 Chrome,自然也是基于 V8 引擎的。神奇的是,就连浏览器界的独树一帜的 Microsoft 也投靠了 Chromium 阵营。另外,Electron 是基于 Node.js 与 Chromium 开发桌面应用,也是基于 V8 的。

V8 由许多子模块构成,其中这 4 个模块是最重要的:

  • Parser:负责将 JavaScript 源码转换为 Abstract Syntax Tree (AST)
  • Ignition:interpreter,即解释器,负责将 AST 转换为 Bytecode,解释执行 Bytecode;同时收集 TurboFan 优化编译所需的信息,比如函数参数的类型;
  • TurboFan:compiler,即编译器,利用 Ignitio 所收集的类型信息,将 Bytecode 转换为优化的汇编代码;
  • Orinoco:garbage collector,垃圾回收模块,负责将程序不再需要的内存空间回收;

其中,Parser,Ignition 以及 TurboFan 可以将 JS 源码编译为汇编代码。Parser 将 JS 源码转换为 AST,然后 Ignition 将 AST 转换为 Bytecode,最后 TurboFan 将 Bytecode 转换为经过优化的 Machine Code(实际上是汇编代码)。

参考: JavaScript 深入浅出第 4 课:V8 引擎是如何工作的?

关于 V8 的几个问题

  1. V8 新生代什么时候会晋升为老生代?

    在 V8 引擎中,新生代的对象在经历两次新生代垃圾回收后,如果还存活,就会被晋升为老生代。也就是说,新生代的对象在经过一次垃圾回收后,如果还存活,会被标记为老生代候选。如果在下一次新生代垃圾回收时仍然存活,就会被晋升为老生代。

  2. 为什么是两次呢,有什么根据吗?

    在 V8 中,新生代的内存空间一般设置得比较小,主要用来存储生命周期短的对象。过于频繁的垃圾回收会导致效率降低,因此 V8 设计了这样一个策略:只有经过两次垃圾回收仍然存活的对象,才会被认为可能是生命周期长的对象,从而被晋升到老生代内存中。这是一种基于经验的策略,因为经过两次垃圾回收仍然存活的对象,大概率上是生命周期长的对象。这样做的目的是为了尽可能地保证新生代的内存空间被高效地利用,同时也尽可能地减少因垃圾回收导致的性能损耗。

  3. 那老生代的标记清除是怎么清除的呢?

    老生代垃圾回收主要采用的是标记-清除(Mark-Sweep)算法。该算法分为两个阶段:标记阶段和清除阶段。
    标记阶段:从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的(可达的)元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
    清除阶段:清除阶段就是把标记阶段标记的垃圾数据清除掉。然后对堆进行整理,但是这个整理并不会像 Scavenge 算法那样进行内存复制,因此会产生内存碎片。

  4. 根元素是啥,为什么是递归遍历?

    根元素是指在垃圾回收中,作为起始点的对象。例如全局对象、活动的函数调用等。这些对象一般都是存储在栈中的,垃圾回收器会从这些根对象开始查找,找到所有从根对象开始引用的对象。
    为什么是递归遍历呢?因为对象之间的引用关系就像一棵树,根对象就是这棵树的根,而由根对象引用的对象就是树的分支。垃圾回收器需要找到所有从根开始的可达对象,因此需要递归遍历所有的引用路径。当找不到新的路径时,递归就结束,这样就可以找到所有的可达对象,而不可达的对象就被判断为垃圾数据。

JSCore

JSCore 是一个由苹果公司开发的 JavaScript 引擎,它用于 WebKit 浏览器引擎,也用于 iOS 中的 Safari 浏览器。JSCore 提供了一种在 JavaScript 和 Objective-C 之间进行交互的方式,使得开发者可以在 iOS 应用中嵌入和执行 JavaScript 代码。JSCore 还能够在没有 UIWebView 或 WKWebView 的情况下执行 JavaScript,这让它在一些特殊的应用场景下,如后台处理、数据处理等,变得非常有用。

JSCore 的组成部分:Lexer、Parser、LLInt 以及 JIT 的部分。

  • Lexer:词法分析,生成 token。
  • Parser:根据 token 进行语法分析,生成 AST,之后 ByteCodeGenerator 会根据 AST 来生成 JSCore 的字节码
  • LLInt 和 JIT: LLInt 来解释执行 ByteCode,当遇到多次重复调用或者是递归,循环等条件会通过 OSR 切换成 JIT 进行解释执行(根据具体触发条件会进入不同的 JIT 进行动态解释)来加快速度

参考: 深入理解 JSCore

QuickJS

QuickJS 是一个小型的 JavaScript 引擎,由 Fabrice Bellard 和 Charlie Gordon 开发。它可以解析并执行 ES2019 规范的 JavaScript 代码,并且包含了一些额外的特性,如数学扩展和大数支持。

一个显著的特性是 QuickJS 支持完全的 JavaScript 虚拟机快照。这意味着你可以在某一时刻将你的 JavaScript 程序的状态完全保存下来,然后在稍后的任何时间恢复并继续运行。这非常适合需要频繁启动或者在低资源环境下运行 JavaScript 的场景。

QuickJS 的二进制版本非常小巧,只有几百 KB,这使得它非常适合嵌入到其它的程序或者系统中。它还提供了一个命令行解释器,可以用来运行 JavaScript 程序或者作为一个交互式的 JavaScript shell 使用。

除了可以在浏览器和 Node.js 之外运行 JavaScript 之外,QuickJS 提供了一个新的选择,特别是在嵌入式系统或者资源受限的环境下。

参考:深入剖析 JavaScript 编译器/解释器引擎 QuickJS