执行上下文和执行栈
一、执行上下文的定义
执行上下文(Execution Context),就是当前 JS 代码被解析和执行时所在环境的抽象概念,JS 中运行任何的代码都是在执行上下文中运行的。
二、执行上下文的类型
1、全局执行上下文
这是默认的的执行上下文,不在任何函数中的代码都位于全局执行上下文中。
全局执行上下文做了两件事:
- 创建一个全局对象。
- 将
this
指针指向这个全局对象。
一个程序中只能存在一个全局执行上下文。
2、函数执行上下文
每个函数都有自己的执行上下文,在函数被调用时会被创建。
一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤。
3、Eval 函数执行上下文
运行在 eval
函数中的代码也有自己的执行上下文。
三、执行上下文的生命周期
执行上下文的生命周期包括三个阶段:
- 创建阶段
- 执行阶段
- 回收阶段
1、创建阶段
创建阶段在函数被调用,但未执行任何其内部代码之前,会做以下三件事:
- 创建变量对象
首先初始化函数的参数 arguments
,提升函数声明和变量声明。
- 创建作用域链
在执行上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JS 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
- 确定 this 指向
在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为 undefined
,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。
另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出 this
、arguments
和函数的参数。
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
会终止可执行代码的执行,因此会直接将当前上下文弹出栈。