加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

V8是如何快速地解析JavaScript延迟解析

发布时间:2019-05-29 11:55:20 所属栏目:建站 来源:Web前端程序员
导读:解析是将源代码转换成一个中间表示形式供编译器使用的步骤(在V8中,是字节码编译器Ignition)。解析和编译发生在web页面启动的关键路径上,在启动期间,并不是所有提供给浏览器的函数都需要被调用。尽管开发人员可以使用异步和延迟脚本来延迟这些代码的加载
副标题[/!--empirenews.page--]

解析是将源代码转换成一个中间表示形式供编译器使用的步骤(在V8中,是字节码编译器Ignition)。解析和编译发生在web页面启动的关键路径上,在启动期间,并不是所有提供给浏览器的函数都需要被调用。尽管开发人员可以使用异步和延迟脚本来延迟这些代码的加载,但这并不总是可行的。此外,许多web页面的代码只能被特定的特性使用,这样一来,在每个页面单独运行期间,用户是根本无法访问这些代码的。

V8是如何快速地解析JavaScript延迟解析

急切地编译不必要的代码会产生实际的资源成本:

  • 创建这些不必要的代码会占用CPU的一部分时间,这会导致启动时实际需要的代码延迟加载。
  • 代码对象会占用内存,至少在回收机制判定当前代码不再需要并允许垃圾收集器回收之前是这样的。
  • 顶级脚本结束执行时编译的代码最终会缓存在磁盘上,占用磁盘空间。

由于这些原因,所有主流浏览器都实现了延迟解析。以前的做法是为每个函数生成一个抽象语法树(AST),然后将其编译为字节码,而使用了延迟解析之后,解析器就可以“预解析”它遇到的函数,而不需要对这些函数进行完全解析。它通过切换到预解析器来实现这一点,而预解析器是解析器的一个副本,它只做最基本的工作,否则就会跳过该函数。预解析器验证它跳过的函数在语法上是否是有效的,并生成正确编译外部函数所需的所有信息。在后边调用预解析的函数时,将按需对其进行完全解析和编译。

变量分配

使预解析复杂化的主要问题是变量分配。

出于性能原因,函数激活是在机器堆栈上进行管理的。例如,如果函数g调用了参数为1和2的函数f:

V8是如何快速地解析JavaScript: 延迟解析

首先将接收器(即f的this值,由于它是一个草率的函数调用,所以它是globalThis)推入堆栈,接着是被调用的函数f。然后再将参数1和2推入堆栈。此时函数f被调用。为了执行调用,我们首先将g的状态保存在堆栈上: 包括f的“返回指令指针”(rip;我们需要返回什么代码)以及“帧指针”(fp;返回时堆栈应该是什么样子的)。然后我们输入f,它为局部变量c分配空间,以及它可能需要的任何临时空间。这确保了当函数激活超出作用域时,函数使用的任何数据都会消失: 它只是从堆栈中弹出。

V8是如何快速地解析JavaScript: 延迟解析

对带有参数a,b和局部变量c的函数f的调用的堆栈分配布局。

这种设置的问题是函数可以引用在外部函数中声明的变量。内部函数存活的时间可能会比它们被创建时的激活时间要长:

V8是如何快速地解析JavaScript: 延迟解析

在上面的例子中,从inner到make_f中声明的变量d的引用会在make_f返回后进行计算。为了实现这一点,使用词法闭包的语言的虚拟机会在一个称为“上下文”的结构中分配从堆上的内部函数中引用的变量。

V8是如何快速地解析JavaScript: 延迟解析

通过将make_f的参数复制到一个上下文中来对它进行调用,该调用的堆栈布局会在堆上进行分配,供捕捉d的inner稍后使用。

这意味着对于函数中声明的每个变量,我们需要知道内部函数是否引用了该变量,以便决定是在栈上分配该变量,还是在堆上分配的上下文中分配该变量。当我们计算一个函数的字面量时,我们分配一个闭包,它指向函数的代码和当前上下文: 包含函数可能需要访问的变量值的对象。

长话短说,我们至少需要跟踪预解析器中的变量引用。

如果我们只跟踪引用,就会过多估计引用的变量。在外部函数中声明的变量可以通过内部函数中的重新声明来隐藏,从而创建一个来自该内部函数的引用,并将其指向内部声明,而不是外部声明。如果我们无条件地在上下文中分配外部变量,程序性能就会受到影响。因此,要使变量分配能正确地处理预解析过程,我们需要确保预解析后的函数正确地跟踪变量引用和声明。

顶层代码是这条规则的一个例外。一个脚本的顶层总是堆分配的,因为变量在脚本之间是可见的。接近良好工作的体系结构的一个简单方法是简单地运行预解析器,而不需要对快速解析的顶层函数进行变量跟踪;并为内部函数使用完整的解析器,但在编译的时候跳过它们。这比预解析过程成本更高,因为我们不需要构建整个AST,但它使我们启动并运行。这正是V8在新版本V8 v6.3 / Chrome 63中所做的。

向预解析器说明变量的情况

跟踪预解析器中的变量声明和引用是非常复杂的,因为在JavaScript中,某些部分表达式的含义从一开始就不清楚。例如,假设我们有一个带参数d的函数f,它有一个内部函数g,从表达式看起来g可能引用了d。

V8是如何快速地解析JavaScript: 延迟解析

它最终可能确实会引用d,因为我们看到的tokens标记是析构赋值表达式的一部分。

V8是如何快速地解析JavaScript: 延迟解析

它最终也可能是一个带有析构参数d的箭头函数,在这种情况下,f中的d就没有被g引用。

V8是如何快速地解析JavaScript: 延迟解析

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读