探秘 Node.js 10 新功能的背后:V8 release 6.6
每六周,我们都会创建一个 V8 的新分支,作为我们发布流程的一部分。每个版本都是在 Chrome Beta 里程碑之前从 V8 的 Git master 分支出来的。今天(2018-03-27),我们很高兴地宣布,我们发布了一个新的分支:V8 version 6.6,在几个星期内,我们会发布 Chrome 66 Stable 版,在此之前它依然处于测试阶段。V8 v6.6 提供了面向开发人员的一些很酷的特性。本文提供了预期发布的一些亮点预览。
JavaScript 语言新特性
Function.prototype.toString() 现在返回源代码的所有内容,包括空格和注释。以下是一个旧行为和新行为的对比例子:
// 注意 `function` 关键词之前的注释以及空格 function /* a comment */ foo () {} // 之前版本: foo.toString(); // → 'function foo() {}' // ^ no comment // ^ no space // 新版本: foo.toString(); // → 'function /* comment */ foo () {}'
行分隔符(U+2028)和段落分隔符(U+2029)现在允许出现在字符串文字中,matching JSON。以前,这些符号被视为字符串中的行结束符,因此使用它们会导致 SyntaxError
异常。
在异常捕获的 catch
子句中可以不加参数: catch 的参数可以省略了 optional-catch-binding。如果您不需要在异常代码中处理 exception 对象,这非常有用。
try { doSomethingThatMightThrow(); } catch { // → Look mom, no binding! handleException(); }
除了 String.prototype.trim()
外,V8 现在实现了 String.prototype.trimStart() 和 String.prototype.trimEnd()。此功能以前是通过非标准 trimLeft()
和 trimRight()
方法提供的,这些方法仍然是新方法的别名,用于向后兼容。
const string = ' hello world '; string.trimStart(); // → 'hello world ' string.trimEnd(); // → ' hello world' string.trim(); // → 'hello world'
Array.prototype.values() 方法返回了数组的迭代接口,就像 ES2015 的 Map
和 Set
一样:现在我们可以使用 keys
、values
、entries
进行遍历。此更改有可能与现有的 JavaScript 代码不兼容。如果您发现某个网站有奇怪的行为或代码运行中断了,请尝试通过 chrome://flags/#enable-array-prototype-values
禁用此功能,并提出问题。
缓存执行过的代码
“冷加载(cold load)”和“温加载(warm load)”这两个术语在关于加载性能方面是众所周知的。在 V8 中,还有热加载(hot load)的概念。我们以 Chrome 为例说明加载的不同级别:
- 冷加载(cold load):Chrome 首次看到访问的网页,并且根本没有任何数据缓存。
- 温加载(warm load):Chrome 会记住网页已被访问,并且可以从缓存中检索某些资源(例如图像和脚本源文件)。V8 意识到页面使用了相同的脚本文件,因此将编译后的代码与脚本文件一起缓存在磁盘缓存中。
- 热加载(hot load):Chrome 第三次访问网页时,从磁盘缓存载入脚本文件时,它还向 V8 提供上次加载期间缓存的代码。V8 可以使用这个缓存代码来避免必须从头开始解析和编译脚本。
在 V8 v6.6 之前,我们在顶层编译后立即缓存生成的代码。V8 只编译已知在顶层编译过程中立即执行的函数,并将其他函数标记为延迟编译。这意味着缓存代码只包含顶级代码,而所有其他函数必须在每次页面加载时从头开始进行延迟编译。从版本 6.6 开始,V8 会缓存顶级代码执行之后的脚本生成的代码。在我们执行脚本时,更多的函数会被编译并且可以被包含在缓存中。因此,这些函数不需要在未来页面加载时编译,从而将热加载(hot load)场景中的编译和解析时间缩短 20-60%。对最终用户可见的是,提供了一个不太拥挤的主线程,因此会更顺畅,而且有更快的加载体验。
之后我们会编写此主题相关的详细博客文章。(已经发布并翻译:V8 6.6 进一步改进缓存性能)
后台编译
一段时间以来,V8 已经能够在后台线程上解析 JavaScript 代码。随着去年发布的 V8 新的 Ignition 字节码解释器,我们扩展了这个功能,以便在后台线程上将 JavaScript 源代码编译为字节码。这使得嵌入 V8 引擎的软件可以在主线程中执行更多工作,来执行更多的 JavaScript 脚本。我们在 Chrome 66 中启用了此功能,在通常的网站上,主线程编译时间减少了 5% 到 20%。有关更多详细信息,请参阅[此功能的最新博客文章(https://v8project.blogspot.co...。
移除 AST 编码
去年 Ignition 和 TurboFan 推出后,我们继续从简化编译流水线(pipeline)中获益。以前在代码解析(parsing)后还需要一个名为 "AST Numbering(AST编码)" 的阶段,用来对生成的抽象语法树中的节点进行编号,之后编译器使用此节点时可以有共同的引用点。
随着时间的推移,这个后处理过程(post-processing)已经扩展到包含其他功能:为生成器和异步函数的暂停点进行编号,收集需要迫切编译的内部函数,初始化文字字面量或检测不可优化的代码模式。
通过新的流水线(pipeline),Ignition 字节码成为常用的引用点,并且不再需要编号 - 但是,仍然需要其它的功能,并且仍然保留了 AST 编号。
在 V8 v6.6 中,我们终于设法移除了其余的功能或将其移动到了其他地方,这些工作在解析过程中完成,从而避免了对 AST 的遍历。这导致实际编译时间提高了 3-5%。
异步性能改进
我们为 Promise 和异步函数取得了一些不错的性能改进,特别是设法缩小了异步函数和 promise 链之间的差距。
此外,异步生成器和异步迭代的性能也得到显着提高,在即将发布的 Node 10 LTS 版中会包含 V8 v6.6。
作为一个例子,考虑下面的斐波那契序列:
async function* fibonacciSequence() { for (let a = 0, b = 1;;) { yield a; const c = a + b; a = b; b = c; } } async function fibonacci(id, n) { for await (const value of fibonacciSequence()) { if (n-- === 0) return value; } }
我们已经测量了这种模式在 Babel transpilation 之前和之后的改进:
最后,字节码改进也提高了这些“可暂停”函数的运行时性能:生成器,异步函数和模块,并减少了它们的编译大小。我们计划在即将发布的版本中进一步改进异步函数和异步生成器的性能,敬请关注。
数组性能改进
Array#reduce
对于 holey double arrays 吞吐量性能提高了 10 倍以上(请参阅我们的博客文章,了解 “holey 数组”和 “packed 数组”是什么)。
不受信任的代码保护
在 V8 v6.6 中,我们针对旁路信道漏洞采取了更多缓解措施,以防止信息泄露给不可信的 JavaScript 和 WebAssembly 代码。
不再需要 GYP
这是第一个没有 GYP 文件的 V8 正式版本。如果您的产品需要 GYP 文件,则需要自行将它们复制到您自己的源代码库中。
内存分析
Chrome 的 DevTools 现在可以跟踪和快照 C++ DOM 对象,并显示 JavaScript 引用的所有可访问的 DOM 对象。这个特性是 V8 垃圾收集器的新 C++ 跟踪机制的好处之一。欲了解更多信息,请查看专门的博客文章:Chrome 66 使用 DevTools 跟踪 JS 对象和 DOM 对象的引用。
V8 API
请使用 git log branch-heads/6.5..branch-heads/6.6 include/v8.h
获取 API 的变更列表。
对于使用 git 的 V8 开发者,可以通过 git checkout -b 6.6 -t branch-heads/6.6
签出 V8 v6.6 中的新功能进行试验。或者,您可以订阅 Chrome 的 Beta 频道,这样可以尽快尝试新功能。