执行上下文
概念
执行上下文
当 JS
引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作”,就叫做 “执行上下文(execution context 简称 EC
)” 或者也可以叫做执行环境。
ES6 官方 中的执行上下文定义:
执行上下文是一种规范策略,用于跟踪ECMAScript实现对代码的运行时评估。在任何时候,最多只有一个执行上下文在实际执行代码。这被称为运行执行上下文。堆栈用于跟踪执行上下文。正在运行的执行上下文始终是这个堆栈的顶部元素。每当控制从与当前运行的执行上下文关联的可执行代码转移到与该执行上下文不关联的可执行代码时,就会创建一个新的执行上下文。新创建的执行上下文被推到堆栈上,并成为正在运行的执行上下文。
词法环境
ES6 官方 中的词法环境定义:
词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。
简单来说:
词法环境由环境记录与外部环境引入记录两个部分组成。
其中环境记录用于存储当前环境中的变量和函数声明的实际位置;外部环境引入记录很好理解,它用于保存自身环境可以访问的其它外部环境
变量环境
执行上下文的LexicalEnvironment和VariableEnvironment组件始终是词法环境。创建执行上下文时,它的LexicalEnvironment和VariableEnvironment组件最初具有相同的值。
变量环境 它也是一个 词法环境 ,所以它有着词法环境的所有特性。
在 ES6
中,词法环境组件和 变量环境组件的一个不同就是前者被用来存储函数声明和变量(let
和 const
)绑定,而后者只用来存储 var
变量绑定。
ES6 官方 对var
和let、const
的说明:
var
语句声明了作用域为运行执行上下文的变量环境(VariableEnvironment)的变量。Var变量在实例化其包含的词法环境时被创建,在创建时被初始化为undefined
。
let
和const
声明定义了作用域为运行执行上下文的词法环境(LexicalEnvironment)的变量。变量是在实例化其包含的词法环境时创建的,但是在变量的LexicalBinding计算完成之前,不能以任何方式访问。由LexicalBinding和Initializer定义的变量在计算LexicalBinding时,而不是在创建变量时,被赋予Initializer的AssignmentExpression的值。如果let声明中的LexicalBinding没有Initializer,那么在对LexicalBinding求值时,将给变量分配未定义的值。
这就解释了:
var
为什么会存在变量声明提升:var
声明的变量在创建时,会被初始化为undefined,并且可以访问;let
和const
为什么会存在暂时性死区:let
和const
声明的变量在创建时不会被初始化,此时不能以任何方式访问。
执行上下文类型
执行上下文有三种:
全局执行上下文
、函数执行上下文
、Eval函数执行上下文
全局执行上下文
全局执行上下文只有一个,在客户端中,一般由浏览器创建
,也就是window对象,可以通过this
访问。
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们在全局环境中通过var创建的变量或函数,都可以通过window直接访问。
函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文
。
Eval函数执行上下文
执行在 eval
函数内部的代码也会有它属于自己的执行上下文,但由于并不经常使用 eval
,所以在这里不做分析。
eval()
函数会修改已经存在的词法作用域,因此不推荐使用
执行上下文栈
当一段脚本运行起来的时候,可能会调用很多函数并产生很多函数执行上下文,那么问题来了,这些执行上下文该怎么管理呢?为了解决这个问题,javascript
引擎就创建了 “执行上下文栈” (Execution context stack
简称 ECS
)来管理执行上下文。
顾名思义,执行上下文栈是栈结构的,因此遵循 LIFO
(后进先出)的特性,代码执行期间创建的所有执行上下文,都会交给执行上下文栈进行管理。
当 JS 引擎开始解析脚本代码时,会首先创建一个全局执行上下文,压入栈底(这个全局执行上下文从创建一直到程序销毁,都会存在于栈的底部)。
每当引擎发现一处函数调用,就会创建一个新的函数执行上下文压入栈内,并将控制权交给该上下文,待函数执行完成后,即将该执行上下文从栈内弹出销毁,将控制权重新给到栈内上一个执行上下文。
执行上下文的创建
执行上下文的创建主要负责三件事:
确定this---创建词法环境组件(LexicalEnvironment)---创建变量环境组件(VariableEnvironment)
创建过程的伪代码表示:
ExecutionContext = {
// 确定this的值
ThisBinding = <this value>,
// 创建词法环境的组件
LexicalEnvironment = { ... },
// 创建变量环境的组件
VariableEnvironment = { ... },
}
全局执行上下文的创建
程序启动,全局上下文被创建
创建全局上下文的词法环境
- 创建对象环境记录器,它用来定义出现在全局上下文中的变量和函数的关系(负责处理
let
和const
定义的变量) - 创建外部环境引用,值为
null
- 创建对象环境记录器,它用来定义出现在全局上下文中的变量和函数的关系(负责处理
创建全局上下文的变量环境
- 创建对象环境记录器,它持有变量声明语句在执行上下文中创建的绑定关系(负责处理
var
定义的变量,初始值为undefined
造成声明提升) - 创建外部环境引用,值为
null
- 创建对象环境记录器,它持有变量声明语句在执行上下文中创建的绑定关系(负责处理
确定
this
值在浏览器中,值为全局对象
window
函数执行上下文的创建
函数被调用,函数上下文被创建
创建函数上下文的词法环境
- 创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的
arguments
对象(此对象存储索引和参数的映射)和传递给函数的参数的 length(负责处理let
和const
定义的变量) - 创建外部环境引用,值为全局对象或父级词法环境
- 创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的
创建函数上下文的变量环境
- 创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的
arguments
对象(此对象存储索引和参数的映射)和传递给函数的参数的 length(负责处理var
定义的变量,初始值为undefined
造成声明提升) - 创建外部环境引用,值为全局对象或父级词法环境
- 创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的
确定
this
值this
的值取决于函数的调用方式,如果被一个对象调用,那么this
指向这个对象。否则this
一般指向全局对象window
或者undefined
(严格模式)
执行上下文创建过程(伪代码)
//全局执行上下文
GlobalExectionContext = {
// this绑定为全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
//环境记录
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 let const创建的变量a b在这
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
// 全局环境外部环境引入为null
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 var创建的c在这
c: undefined,
}
// 全局环境外部环境引入为null
outer: <null>
}
}
// 函数执行上下文
FunctionExectionContext = {
//由于函数是默认调用 this绑定同样是全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 arguments对象在这
Arguments: {0: 20, 1: 30, length: 2},
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 var创建的g在这
g: undefined
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
}
}