桔妹导读:在我们 Hummer 跨端技术框架 的研发过程中,不可避免会对 JavaScript 引擎有所探索和研究。只有深入了解了 JavaScript 的工作原理,才能在跨端研发的诸多细节上避免踩坑,并且做出更好地调优工作。对于很多前端同学来说,JavaScript 引擎就像一个难以触及的黑盒,既熟悉又陌生,因为它被内置在了浏览器内核中。即使在平时开发过程中天天和 JavaScript 引擎打交道,但大多也只是知道 JavaScript 引擎可以解释执行 JavaScript 代码,对于其内部实现原理并不是特别了解。所以我们接下来会专门花几个专题,来深入剖析一下 JavaScript 引擎的世界,逐步揭开它的神秘面纱。这一期我们主要讲一下 JavaScript 引擎中的 “JSValue 的内部实现”。
1.
前言
许多现代编程语言都具有称之为动态类型的功能。动态类型语言和静态类型语言之间的主要区别在于,大多数类型检查是在运行时执行的,而不是在编译时执行的。类型不再与变量关联,而是与内部存储的基础值关联,本文将以 JavaScript 为例进行分析。
2.
实现方式
实现 JavaScript 引擎的第一步是实现值的表示形式,这其实有一定的难度,因为 JS值 可以是几种不同的类型中的任何一种:
undefined
null
boolean
number (double)
reference (string, Symbol, Object, etc)
要实现 动态类型 就需要一种能够表示上面所有类型的数据结构。实现这样的值类型主要有以下几种方式:
tagged 方式
-
tagged unions(QuickJS)
tagged pointer(V8)
boxing 方式
-
nan-boxing(JavaScriptCore)
nun-boxing & pun-boxing(SpiderMonkey)
下面分别来详细介绍下这些实现方式,以及这些方式对应的落地 JavaScript 引擎:
▍1. tagged unions
先来看下 QuickJS 中比较直接的一种实现方式:
▎QuickJS
#else /* !JS_NAN_BOXING */
typedef union JSValueUnion {
int32_t int32;
double float64;
void *ptr;
} JSValueUnion;
typedef struct JSValue {
JSValueUnion u;
int64_t tag;
} JSValue;
#define JSValueConst JSValue
这其实是 tag + struct 的改进版。使用 union 可减少一定的内存使用。
但缺点是不论 JSValue 表示 int32 还是 指针 类型。都需要 16 个字节(以在双精度浮点数或 64 位指针或 int64 上保持 8 字节对齐)。
那么是否有更好的 JSValue 表示方法呢?能否压缩到只用8字节呢?接下来我们先来看 JavaScriptCore 的实现。
▍2. nan-boxing
在开始之前,我们需要一些准备知识。IEEE 754 标准。在下文所提标准中,如无特殊说明,均为 IEEE 754,且以 64 架构为例。
▎double
关于 double 的定义可以根据 维基百科的相关链接 查看。这里我们主要摘录其格式:
sign: 表示正负,0为正,1为负
exponent: 指数位
fraction: 尾数
以 0.3 为例:
二进制格式:
0b0011111111010011001100110011001100110011001100110011001100110011