Skip to main content

执行上下文和执行栈

一、执行上下文的定义

执行上下文(Execution Context),就是当前 JS 代码被解析和执行时所在环境的抽象概念,JS 中运行任何的代码都是在执行上下文中运行的。

二、执行上下文的类型

1、全局执行上下文

这是默认的的执行上下文,不在任何函数中的代码都位于全局执行上下文中。

全局执行上下文做了两件事:

  1. 创建一个全局对象。
  2. this 指针指向这个全局对象。

一个程序中只能存在一个全局执行上下文。

2、函数执行上下文

每个函数都有自己的执行上下文,在函数被调用时会被创建。

一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤。

3、Eval 函数执行上下文

运行在 eval 函数中的代码也有自己的执行上下文。

三、执行上下文的生命周期

执行上下文的生命周期包括三个阶段:

  • 创建阶段
  • 执行阶段
  • 回收阶段

1、创建阶段

创建阶段在函数被调用,但未执行任何其内部代码之前,会做以下三件事:

  • 创建变量对象

首先初始化函数的参数 arguments,提升函数声明和变量声明。

  • 创建作用域链

在执行上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JS 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。

  • 确定 this 指向

在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为 undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。

另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出 thisarguments 和函数的参数。

2、执行阶段

执行变量赋值、代码执行。

3、回收阶段

执行上下文出栈等待虚拟机回收执行上下文。

四、执行栈

函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那要如何管理创建的执行上下文呢?

JS 引擎创建了执行上下文栈(即执行栈,Execution Context Stack)来管理执行上下文,可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循 LIFO(先进后出)的原则。

  • JS 执行在单线程上,所有的代码都是排队执行。

  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。

  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。

  • 当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。

  • 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。

  • 全局上下文有且只有一个,它在浏览器关闭时出栈。

举个例子:

var color = 'blue';

function changeColor() {
var anotherColor = 'red';

function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}

swapColors();
}

changeColor();

上述代码运行按照如下步骤:

  • 上述代码在浏览器中加载时,首先是全局上下文(Global Context)入栈。

  • 遇到 changeColor(),会创建一个 changeColor 的执行上下文(EC),然后将其压入到执行栈(ECStack)中。

  • 执行 changeColor 时调用 swapColors 函数,同样地会创建了一个 swapColors 的执行上下文,并压入执行栈中。

  • swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。

  • changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。

注意:函数如果遇到 return 会终止可执行代码的执行,因此会直接将当前上下文弹出栈。