面试官:说说JS中的执行上下文
⭐说说JS的执行上下文
执行上下文是JavaScript代码执行环境的抽象概念,执行上下文可以看作是一个环境,其中包含了当前代码的变量、函数、this 指向等信息。
但要注意执行上下文与作用域的区别:作用域指的是变量、函数和对象在代码中可访问的范围。它定义了变量的可见性和生命周期。作用域规定了在何处以及如何查找变量。
可以说执行上下文是为了实现作用域而存在的一种机制,但它们并不是完全等同的概念。
执行上下文分为三类:
- 全局执行上下文:执行一段JS代码前,会先为它创建全局执行上下文,并将this指向全局对象(在浏览器中是window,node环境中是global)全局执行上下文只会被创建一次,随着程序的开始而创建,随着程序的关闭而销毁。
- 函数执行上下文:执行遇到函数时,就会为这个函数创建一个函数执行上下文,但这里的this和函数执行上下文并不等同,只是上下文中保存有this信息
- Eval 函数执行上下文: 指的是运行在
eval
函数中的代码,很少用而且不建议使用
🌰举个栗子:
//这里是全局执行上下文
var globalProperty = 0
function fun1() {
//这里是fun1的函数执行上下文
var fun1Property = 1
function fun2() {
//这里是fun2的函数执行上下文
var fun2Property = 2
}
function fun3() {
//这里是fun3的函数执行上下文
var fun3Property = 3
}
}
每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问。
例如上例中:
- fun1中可以访问全局执行上下文中的globalProperty,而不能访问内部fun2 fun3中的变量
- 同理,fun2 fun3可以访问外部的fun1Property以及最外层的globalProperty
执行上下文中存了些什么?
在执行上下文被创建时:
- 绑定this,普通声明的函数在执行时才能确定绑定的this值,而es6中箭头函数是词法绑定,被创建时就绑定了所在上下文的this
- LexicalEnvironment(词法环境) 组件创建
- VariableEnvironment(变量环境) 组件创建
//伪代码流程
ExecutionContext = {
ThisBinding = <this value>, // 确定this
LexicalEnvironment = { ... }, // 词法环境
VariableEnvironment = { ... }, // 变量环境
}
词法环境:
词法环境是一种持有标识符—变量的映射的结构(简单理解为就是保存了我们定义的变量和函数)
词法环境的组成:
- 环境记录器:是存储变量和函数声明的实际位置。
- 外部环境的引用:意味着它可以访问其父级词法环境。
词法环境有两种类型:
- 全局环境:没有外部环境引用的词法环境(环境记录器指向空),它里面保存了JS内置对象、用户定义的全局变量/函数,this绑定为全局对象。
- 函数环境:用户在函数内部定义的变量存储在环境记录器中,外部环境引用的是外部执行上下文。
根据词法环境的类型不同,环境记录器也分为两类:
-
全局环境中——对象环境记录器:用来定义出现在全局上下文中的变量和函数的关系,当一个变量或函数在对象环境中被定义或声明时,对象环境记录器会使用一个对象来管理这些变量和函数的绑定关系,通过关联的对象,对象环境记录器实现了对变量和函数的访问和操作 🌰举个栗子:
var obj = { x: 10, y: function() { console.log('Hello'); } }; console.log(obj.x); // 输出:10 obj.y(); // 输出:Hello
在这个例子中,
obj
对象包含了x
属性和y
方法。通过对象环境记录器,我们可以通过obj.x
访问对象的属性值,并通过obj.y()
调用对象的方法。 -
函数环境中——声明式环境记录器:存储变量、函数和参数。
词法环境与对象环境记录器在内部机制上有所不同。其中最明显的区别是词法环境使用一个特殊的数据结构——词法环境记录器(Lexical Environment Record),而对象环境记录器则使用一个普通的 JavaScript 对象
//词法环境伪代码
GlobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录器:存储变量和函数声明的实际位置
Type: "Object",
// 在这里绑定标识符
}
outer: <null> // 对外部环境的引用:可以访问其父级词法环境
}
}
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 在这里绑定标识符
}
outer: <Global or outer function environment reference>
}
}
变量环境:
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性
在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( let
和 const
)绑定,而后者仅用于存储变量( var
)绑定
例如:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
👇执行上下文
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { // 词法环境
EnvironmentRecord: {
Type: "Object",
// 在这里绑定标识符
a: < uninitialized >, // let、const声明的变量
b: < uninitialized >, // let、const声明的变量
multiply: < func > // 函数声明
}
outer: <null>
},
VariableEnvironment: { // 变量环境
EnvironmentRecord: {
Type: "Object",
// 在这里绑定标识符
c: undefined, // var声明的变量
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { // 词法环境
EnvironmentRecord: {
Type: "Declarative",
// 在这里绑定标识符
Arguments: {0: 20, 1: 30, length: 2}, // arguments对象
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: { // 变量环境
EnvironmentRecord: {
Type: "Declarative",
// 在这里绑定标识符
g: undefined // var声明的变量
},
outer: <GlobalLexicalEnvironment>
}
}
在其中,let和const定义的关联值设为了uninitialized,说明为初始化,而var则被初始化为了undefined,这就是let/const和var的本质区别
综上,JS程序执行的全过程大致可以描述为:
程序启动,全局执行上下文被创建,压入调用栈
-
创建全局上下文的 词法环境
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
let
和const
定义的变量) - 创建 外部环境引用,值为
null
- 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理
-
创建全局上下文的 变量环境
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
var
定义的变量,初始值为undefined
造成声明提升) - 创建 外部环境引用,值为
null
- 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理
-
确定
this
值为全局对象(以浏览器为例,就是window
)
函数被调用,函数执行上下文被创建,压入调用栈
-
创建函数上下文的 词法环境
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments
对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理let
和const
定义的变量) - 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
创建函数上下文的 变量环境
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
arguments
对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理var
定义的变量,初始值为undefined
造成声明提升) - 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
- 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的
-
确定
this
值
进入函数执行上下文的执行阶段:
- 在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。
参考文章:
转载自:https://juejin.cn/post/7281465630494081035