JS执行上下文

1,699 阅读4分钟

执行上下文,即context,也不知道是谁翻译的,不少的文献、书籍用的都是这个词。还记得第一次接触这个词时的惆怅、迷惘、不知所措,扶了扶眼镜,翻开大辞典,还是翻译成环境比较接地气。JS执行上下文,即JS的执行环境。

执行环境(Execution Context, EC)

当我们的代码执行时,会进入到不同的执行上下文,即不同的环境。在不同的环境中,有着不同的 scope(作用域),代码所能访问到的资源也就不同。 在 JS 中,执行环境有如下三种情况:

  • 全局环境

    代码默认运行的环境,代码执行时会首先进入全局环境。它是最外围的一个执行环境,根据 ECMAScript 实现所在的宿主环境的不同,表示全局环境的对象也不一样。在 web 浏览器中,全局环境就是 window 对象。全局变量和函数都是作为全局对象 window 的变量和方法来创建的。

  • 函数环境

    函数被调用执行时,所创建的执行环境。

  • eval

    使用 eval 会进入一个新的执行环境,它的变量对象为全局变量对象或调用者的变量对象。由于 eval 的毒瘤属性,一般不推荐使用,可忽略。

某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在环境中的变量和函数也随之销毁。这些变量和函数保存在一个叫做变量对象的对象(variableObject)中,关于变量对象将在变量对象与作用域链一文作详细探讨。

执行环境的生命周期

执行环境的生命周期大概分为两个阶段,即创建阶段和执行阶段:

1. 创建阶段

  • 创建作用域链(变量对象+父级执行环境的变量对象)
  • 创建变量对象(包括局部变量、函数以及函数参数)
  • 确定 this 的指向

由此,一个执行环境可以由包含作用域链、变量对象和 this 指针的对象组成:

executionContextObj = {
  scopeChain: {},
  variableObject: {},
  this: {}
}

2. 代码执行阶段

  • 指定变量的值和函数的引用
  • 解释并执行代码

执行环境栈(Execution Context Stack, ECS)

浏览器中的解释器被实现为单线程,同一时间只能处理一个任务,JS 程序中多个执行环境会以栈的方式来处理,这个栈叫做执行栈。栈底永远都是全局环境(窗口关闭时弹出),栈顶就是当前正在执行的环境。前述三种情况都会创建执行环境,执行环境创建时会被压入栈顶,成为一个运行(活动)的环境,位于栈顶的环境执行完毕后就从栈顶弹出,并将环境控制权交给调用者(之前的栈),而调用者继续执行(或激活其他环境),直到它的执行环境结束。ECMAScript 程序中的执行流正是由这个方便的机制控制着。

来看下面的例子:

var firstName = 'snow';

function getName() {
    var lastName = 'John';

    function fullName() {
      var name = lastName + firstName;
      return name;
    }
    var name = fullName();
    return name;
}

getName();

其执行栈变化过程如下:

  • 首先,将全局环境压入栈,开始执行代码,

  • 直到遇到getName(),准备调用函数,创建函数 getName 的执行环境,将其压入栈顶并开始执行函数

  • 直到遇到fullName(),准备调用函数,创建函数 fullName 的执行环境,将其压入栈顶并开始执行函数

    fullName入栈
  • 函数 fullName 执行时没有再生成执行环境,执行完毕后则从栈顶弹出

    fullName出栈
  • fullName 执行栈弹出后,控制权回到了 getName 的执行栈,继续执行代码,执行完毕,从栈顶弹出

  • 最后回到了全局环境,窗口关闭后弹出 执行栈示意图

结论

  • 单线程,同步执行,只有栈顶的环境处于执行中,其余环境需要等待。
  • 执行 JS 程序,首先进入全局环境,全局环境只有一个并在关闭窗口时弹出。
  • 函数调用时会创建新的执行环境,包括调用自己。