1. 执行上下文是什么
简单来说,执行上下文(Execution Context)是指一段 JS 代码在执行时的环境信息(变量、函数、参数、作用域链、this等信息)。
2.执行上下文的分类
-
全局执行上下文:当 JS 程序开始运行时,会自动创建一个全局执行上下文。它代表了整个程序的运行环境,并在整个程序运行期间一直存在。默认会在全局内存中创建两个属性: 1.创建一个 window 对象指向 global 对象 2.将 this 指向 window 对象。
-
函数执行上下文:每当调用一个函数时,都会创建一个新的函数执行上下文。函数执行上下文只在函数执行期间存在,并在函数执行完成后被销毁。默认会创建 arguments 和 this 对象 ,arguments 默认为 { length: 0 },this 的值因调用函数的方式而异。
-
模块执行上下文:当 JS 文件作为模块被导入时,就会创建一个新的模块执行上下文。每个模块都有自己的执行上下文,这意味着每个模块的变量和函数都是私有的,并且不会与其他模块的变量和函数发生冲突。模块执行上下文将在程序退出、模块被替换、 模块不再被引用时被销毁。
-
eval执行上下文:使用 eval 函数执行 JS 代码,则会创建一个 eval 函数执行上下文。
3.执行上下文的结构
-
词法环境(LexicalEnvironment):用来保存标识符与变量值(或函数值)的关系,这个过程叫做绑定。标识符是指变量或函数的名称,变量值是对实际对象或原始值的引用。词法环境由环境记录(EnvironmentRecord)和对外部词法环境(OuterEnv)的引用组成。
-
变量环境(VariableEnvironment):它与词法环境类似,它定义了标识符与变量值(而非函数值)的关系。
主要区别: Lexical Environment 用于存储标识符与变量 (由let和const声明的) 和函数值的绑定,而 Variable Environment 仅用于存储标识符与变量 (由var声明的) 值的绑定。
4.执行栈的结构
执行栈是一种后进先出的数据结构,用于保存代码执行时产生的多个执行上下文。JS 引擎通过它,将每个调用存储到内存中,来跟踪执行上下文。全局执行上下文默认存在于执行栈中,并且位于执行栈底部。在执行全局执行上下文代码时,如果 JS 引擎发现函数调用,它会为该函数创建一个函数执行上下文并将其推送到执行栈的顶部。一旦函数的所有代码都执行完毕,JS 引擎就会取出该函数的执行上下文,并继续执行下面的代码。
让我们通过一个例子来理解这一点:
console.log("Initially I am inside the global execution context.");
let message = "Heyyow!";
function first() {
console.log("I am inside the first function execution context");
second();
console.log("I am again inside the first function execution context");
}
function second() {
console.log("I am inside the second function execution context");
}
first();
console.log("I am back at the global execution context.");
5.执行上下文的生命周期
每个执行上下文都有两个阶段:1.创建阶段 2.执行阶段
以下面这段代码为例:
var name = "Luigi";
let input = "Hello, World!";
function broadcast(message) {
return `${name} says ${message}`;
}
console.log(broadcast(input));
1.创建阶段
创建阶段的执行上下文的结构如下(伪代码):
GlobalExecutionContext = {
LexicalEnvironment : { // 绑定由 let、const 声明的变量,以及函数声明
EnvironmentRecord : {
DeclarativeEnvironmentRecord : {
input: <uninitialized>,
broadcast: < function broadcast(message) {
return `${name} says ${message}`;
} >,
},
ObjectEnvironmentRecord : {
window: < ref. to Global obj. >,
this: < ref. to window obj. >,
},
OuterEnv : < null >, // 引用父级 LexicalEnvironment,全局上下文的父级 LexicalEnvironment 为 null
},
},
VariableEnvironment : { // 绑定由 var 声明的变量
EnvironmentRecord : {
DeclarativeEnvironmentRecord : {
name: undefined,
},
},
},
}
- JS 引擎进入创建阶段
- 创建全局执行上下文并将其推入执行上下文栈的栈顶
- 为 window 对象创建到 Global 对象的绑定
- 为 this 对象创建到 window 对象的绑定,this的值根据函数具体调用方式而有所不同
- 在全局内存中创建一个标识符 name 并用值 undefined 初始化它,这个过程称为提升(hoisting)
- 在全局内存中创建一个标识符 input,而不对其进行初始化(不设置初始值)
- 在全局内存中创建一个标识符 broadcast,并将 broadcast 函数的整个函数定义存储在其中,这个函数也被提升
2.执行阶段
这是 JS 引擎在声明了所有的变量和函数,必要的对象已经绑定之后进入的阶段。每个执行上下文都有一个执行阶段。这个阶段发生的事情很少:变量绑定初始化、变量赋值、可变性和不可变性检查、变量绑定删除、函数调用执行等。
执行阶段的执行上下文的结构如下(伪代码):
GlobalExecutionContext = {
LexicalEnvironment : {
EnvironmentRecord : {
DeclarativeEnvironmentRecord: {
input: "Hello, World!",
broadcast: {
LocalExecutionContext : {
LexicalEnvironment : {
EnvironmentRecord: {
DeclarativeEnvironmentRecord: {
message: "Hello, World!",
},
ObjectEnvironmentRecord: {
arguments: { 0: message, length: 1 }
this: < ref. to window obj. >
},
},
OuterEnv: < ref. to LexicalEnvironment of the GlobalExecutionContext >,
},
},
},
},
ObjectEnvironmentRecord: {
window: < ref. to Global obj. >,
this: < ref. to window Obj. >,
},
OuterEnv: < null >,
},
},
VariableEnvironment : {
EnvironmentRecord : {
DeclarativeEnvironmentRecord: {
name: "Luigi"
},
},
},
}
- JS引擎进入执行阶段
- 获取变量 name 的值并将该值绑定到内存中的标识符
- 获取变量 input 的值并将该值绑定到内存中的标识符
- 遇到 console.log 方法,立即评估其中的参数
- 遇到 broadcast 函数调用,立即为该函数创建一个新的函数执行上下文,并推入执行上下文栈栈顶
- 进入 broadcast 函数执行上下文的创建阶段
- 在该函数的本地内存中创建参数(arguments)对象,初始值为: { length: 0 }
- 将传入的参数 message 添加到参数对象的第一个索引: { 0: message, length: 1 }
- 在函数的本地内存中创建标识符 message,并保存传递给函数调用参数的值
- 进入函数体内部,评估返回语句的返回值
- 看到变量 name,然后在函数的本地内存中查找该变量
- 无法在本地内存中找到标识符 name,因此会继续从其父范围(全局内存)中查找 name
- 在全局内存中找到标识符 name,因此采用该值来替换 name 变量
- 看到变量 message,然后在函数的本地内存中查找该变量
- 它在本地内存中找到标识符 message,因此用该值来替换 message 变量
- 返回 broadcast 函数执行上下文的结果,并将其从执行上下文栈中弹出
- 将控制权与返回结果一起传递给它的调用上下文(全局执行上下文)
- 显示 Luigi says Hello, World!在控制台中
- 全局执行上下文从调用堆栈中弹出,然后 JS 引擎退出
为了帮你进一步理解执行上下文的整个生命周期,请看下图:
相信对上述概念的理解,将有助于你更深入地理解这些概念:提升、作用域链、闭包、this。
参考资料:
blog.openreplay.com/explaining-…
blog.bitsrc.io/understandi…
dev.to/luigircruz/…