likes
comments
collection
share

v8引擎解析js过程以及js执行上下文

作者站长头像
站长
· 阅读数 17

前言

所有 JavaScript 代码都需要在某种环境中托管和运行。在大多数情况下,该环境是 Web 浏览器。

对于要在 Web 浏览器中执行的任何 JavaScript 代码,很多过程都在幕后进行。在本文中,我们将了解 JavaScript 代码在 Web 浏览器中运行的幕后发生的一切。

js引擎

v8引擎

V8 是一个由 Google 开发的开源 JavaScript 引擎,目前用在 Chrome 浏览器和 Node.js 中,其核心功能是执行易于人类理解的 JavaScript 代码。

v8引擎解析js过程以及js执行上下文

v8引擎首先会解析源代码,通过paese模块将js代码转换成AST(抽象语法树),同时还会生成相关的作用域,然后解释器将AST转换成ByteCode(字节码),之后的过程就是代码的执行过程。除了解释器,v8引擎还有编译器,比如一个函数执行多次,可能会被识别为热点函数就会经过编译器优化,将Bytecode编译为Optimized Machine Code,以提高代码的执行性能,然后在执行代码。

v8引擎解析js过程以及js执行上下文

上图中 Ignition是一个解释器,会将AST转换成ByteCode(字节码)

TurboFan是编译器,可以将字节码编译为CPU可以直接执行的机器码,提高代码的执行性能(机器码实际上也会被还原为ByteCode)

最后在贴上一张 我很喜欢的图,帮助大家进一步了解

v8引擎解析js过程以及js执行上下文

FQ

  • V8 启动执行 JavaScript 之前,它还需要准备执行JavaScript 时所需要的一些基础环境,这些基础环境包括了“堆空间”“栈空间”“全局执行上下文”“全局作用域”“事件循环系统”“内置函数”等

  • JavaScript 是一种非常灵活的动态语言,对象的结构和属性是可以在运行时任意修改的,而经过优化编译器优化过的代码只能针对某种固定的结构,一旦在执行过程中,对象的结构被动态修改了,那么优化之后的代码势必会变成无效的代码,这时候优化编译器就需要执行反优化操作,经过反优化的代码,下次执行时就会回退到解释器解释执行。 -js引擎还有其他很多种,这里这是以v8引擎为例。

  • SpiderMonkey,第一款JavaScript引擎,用于Mozilla Firefox

  • JavaScriptCore,用于Safari

执行上下文

执行上下文有三种,

  • 全局执行上下文,
  • 函数执行上下文,
  • eval上下文(由于eval一般不会使用,这里不做讨论)

全局执行上下文 Global Execution Context (GEC)

全局执行上下文只有一个,在客户端中一般由浏览器中的js引擎创建。所有不在函数内部的JavaScript代码都会在其中执行。

对于每个 JavaScript 文件,只能有一个 GEC。

var name = "jimmy"
console.log(window.name) // jimmy

函数执行上下文*Function Execution Context (FEC)*

函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文,需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。

执行上下文的被创建

我们已经了解了什么是执行上下文以及不同种类的执行上下文,现在让我们来看看执行上下文是如何被创建的。

执行上下文(GEC或FEC)的创建分为两个阶段:

  1. 创建阶段
  2. 执行阶段

创建阶段

在创建阶段,执行上下文首先与执行上下文对象(ECO)相关联。执行上下文对象存储了许多重要的数据,执行上下文中的代码在运行时会使用这些数据。

创建阶段主要是下面三个步骤

  • 创建 Activation对象(AO)或 Variable 对象(VO)
  • 创建作用域链
  • 设置 this关键字的值

执行阶段

执行上下文栈(执行栈)

JavaScript是单线程语言,也就是说它只能在同一时间执行一项任务。因此,其他的操作、函数和事件发生时,执行上下文也会被创建。由于单线程的特性,一个堆叠了执行上下文的栈就会被创建,称为执行栈

当浏览器加载脚本,JS引擎从全局上下文也就是默认上下文开始执行代码,所以全局上下文被放在执行栈的最底部。

然后JS引擎再搜索代码中被调用的函数。每一次函数被调用,一个新的FEC就会被创建,并被放置在当前执行上下文的上方。

执行栈最顶部的执行上下文会成为活跃执行上下文,并且始终是JS引擎优先执行。

一旦活跃执行上下文中的代码被执行完毕,JS引擎就会从执行栈中弹出这个执行上下文,紧接着执行下一个执行上下文,以此类推。

让我们借助一个例子来理解这一点

 var a = 10;

  function functionA() {
    console.log("Start function A");

    function functionB() {
      console.log("In function B");
    }

    functionB();
  }

  functionA();

  console.log("GlobalContext");

v8引擎解析js过程以及js执行上下文

一旦上述代码加载到浏览器中,JS 引擎就会将全局执行上下文推送到执行上下文栈中。当functionA从全局执行上下文中调用时,JS 引擎将functionA执行上下文压入执行上下文栈顶并开始执行functionA,当到functionB调用时,JS 引擎将functionB执行上下文压入执行上下文栈顶。当前functionB执行完过后,functionB执行上下文从执行上下文栈中弹出,然后functionA执行完毕过后,functionA从执行上下文栈中弹出。当所有代码执行完毕后,JS 引擎弹出全局执行上下文,JavaScript 的执行结束。

再来一个例子:


function getName() {
    const year = getYear();

    const name = 'Lynn';
    console.log(`${name} ${year} years old this year`);
}

function getYear() {
    return 18;
}

getName(); 

v8引擎解析js过程以及js执行上下文

上面的执行上下文栈执行过程:

  1. 首先创建了全局执行上下文,当前全局执行上下文处于活跃状态。
  2. 全局代码中有2个函数 getName 和 getYear,然后调用 getName 函数,JS引擎停止执行全局执行上下文,创建了新的函数执行上下文,且把该函数上下文放入执行上下文栈顶。
  3. getName 函数里又调用了 getYear 函数,此时暂停了 getName 的执行上下文,创建了 getYear 函数的新执行上下文,且把该函数执行上下文放入执行上下文栈顶。
  4. 当 getYear 函数执行完后,其执行上下文从栈顶出栈,回到了 getName 执行上下文中继续执行。
  5. 当 getName 执行完后,其执行上下文从栈顶出栈,回到了全局执行上下文中。