什么是执行上下文?(ExecutionContext)
在JS引擎解析到一段可执行的代码时,会在执行前做一些“准备工作”,这个准备工作就是JS执行上下文。它是一个抽象的概念,简单来说就是指JS在运行代码时的环境。
执行上下文的类型
- 全局执行上下文
- 任何不在函数内部的代码都在全局执行上下文,在浏览器中,全局执行上下文便是我们熟知的window对象。全局执行上下文只有一个。
- 函数执行上下文
- 只有在函数调用时才会创建上下文,并且可以创建多个。
- Eval函数执行上下文
- 很少用到
JS是如何创建执行上下文的呢?
执行上下文听着比较抽象,可以把它看作是一个object,这个对象中存放着一些关于上下文的信息。
ES3中的执行上下文有什么呢?
– 变量对象
– 对于全局执行上下文来说,变量对象就是window对象,而对于函数执行上下文来说,变量对象是函数中声明的变量和函数。并且是不能访问的。
– 活动对象
– 函数执行的过程中,将变量对象激活为活动对象,变为可以访问。
– this
– 作用域链
– 确定当前执行代码对变量的访问权限
ES5中的执行上下文
ES5对比ES3中做了调整,去除了变量对象和活动对象,以词法环境和变量环境替代。
// ES5的执行上下文
ExecutionContext = {
ThisBinding = this, // this绑定
LexicalEnvironment = { ... }, // 词法环境
VariableEnvironment = { ... }, // 变量环境
}
词法环境(官方定义)
词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。
简单来说,词法环境和ES3中的变量对象是类似的。但词法环境更为复杂,它分为两种类型,全局和函数。其内部的又分环境记录(类似变量对象)和对外部环境的引用(可以访问其他外部的词法环境)。
// 伪代码
// 全局词法环境
GlobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境:对象环境记录
outer: null // 对外部环境的引用
}
}
// 函数词法环境
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative", // 函数环境:声明性环境记录
outer: Gobal || functionExectionContect // 对外部环境的引用(全局或者外层函数执行上下文)
}
}
变量环境
也是词法环境,只是ES5拆出这个概念为ES6服务,变量环境和词法环境的区别就在于前者用来存放
var声明的变量和函数,后者用来存放let和const声明的变量和函数。
看一个?
let a = 20;
const b = 30;
var c;
function add(d) {
var e = 20;
return e + d;
}
c = add(10);
执行上下文是什么样子的呢?
// 全局上下文被创建
GlobalExectionContext = {
ThisBinding: window, // this指向全局对象,因为在浏览器中为window
LexicalEnvironment: { // 全局上下文词法环境创建
EnvironmentRecord: { // 环境记录
Type: "Object",
a: < uninitialized >, // 处理用let声明的a
b: < uninitialized >, // 处理用const声明的b
add: < func > // 处理add函数
}
outer: null // 创建外部环境引用,为null
},
VariableEnvironment: { // 全局上下文变量环境创建
EnvironmentRecord: { // 环境记录
Type: "Object",
c: undefined, // 处理用var声明的c
}
outer: <null> // 创建外部环境引用,为null
}
}
// 函数调用时,函数上下文被创建
FunctionExectionContext = {
ThisBinding: window, // 确定this的值,因为是在全局调用,this为window
LexicalEnvironment: { // 函数上下文词法环境创建
EnvironmentRecord: { // 和全局记录器不同,除了let,const定义的变量,函数以外,还存放arguments对象
Type: "Declarative",
Arguments: {0: 10, length: 1},
},
outer: <GlobalLexicalEnvironment> // 当前引用就为全局对象
},
VariableEnvironment: { // 函数上下文变量环境创建
EnvironmentRecord: {
Type: "Declarative",
g: undefined // 处理用var声明的g
},
outer: <GlobalLexicalEnvironment> // 当前引用就为全局对象
}
}
从上面的伪代码还可以看到,在创建时,用const和let声明的变量a和b存放时是没有初始值的,而用var声明的变量c一开始未设置初始值,但存放在里面值设置为undefined,所以在c赋值前打印它是有值的,为undefined。而a和b调用时因为没有值,就会报错。这就是一直说的变量提升。
JS是如何管理执行上下文的呢?
执行栈 —> 它是一种栈结构,所以遵循后进先出的特性。它用来存储代码执行时创建的所有执行上下文。
第一次解析脚本时,先会创建一个全局执行上下文压入栈底,之后每遇到函数调用,就会创建新的上下文压入栈的顶部,并把控制权交给该上下文。引擎会执行位于栈顶的函数,执行完后该上下文从栈顶弹出,把控制权交给当前栈顶的那个上下文。