要想学会JS,正确理解执行上下文和执行栈是至关重要的。只有理解了执行上下文和执行栈,才能更好地理解其他 JavaScript 概念(如变量声明提升,作用域和闭包),让我们开始学习吧!
执行栈和执行上下文
执行栈
执行栈,也叫调用栈,用于存储在代码执行期间创建的所有执行上下文。
首次运行JS代码时,会创建一个全局执行上下文,并push到当前执行栈中。每当发生函数调用,JS引擎都会为该函数创建一个新的函数执行上下文,并push到当前执行栈的栈顶。
根据执行栈LIFO的规则,当栈顶函数运行完成后,其对应的函数执行上下文会从执行栈中pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
当所有代码执行完毕,JS引擎从当前栈中移除全局执行上下文。
执行上下文
执行上下文是当前JS代码被解析和执行所在环境的抽象概念。
执行上下文分为三种类型:
- 全局执行上下文:在浏览器中一般指的是window对象。
- 函数执行上下文:每次调用函数都会创建一个新的函数执行上下文。
Eval函数执行上下文:~~~~运行在 eval 函数中的代码,少用并且不建议使用
执行上下文的创建
执行上下文的创建分为两个阶段:创建阶段、执行阶段。
创建阶段
-
确定this的值(thisBinding)
在全局执行上下文中,this的值指向全局对象。
在函数执行上下文中,this的值取决于函数的调用方式(默认绑定、隐式绑定、显式绑定、new绑定、箭头函数)。
-
创建词法环境(lexicalEnvironment)
词法环境是一种规范类型,用于定义标识符和具体变量和函数的关联.
词法环境由两个组件组成:
(1). 环境记录器:存储变量和函数声明的实际位置。
环境记录器有两种类型:
声明式环境记录器(Declarative):存储变量、函数、参数
对于函数环境,Declarative类型的环境记录器还包含一个传递给函数的arguments对象和传递给函数的参数的length。
对象环境记录器(Object):定义出现在全局上下文中的变量和函数的关系。
总结:
在全局环境中,环境记录器是Object类型的;在函数环境中,环境记录器是Declarative类型的。
(2). 一个外部环境的引用:意味着它可以访问其父级词法环境(及作用域)
词法环境有两种类型:
(1). 全局环境
(1). 函数环境
-
创建变量环境(variableEnvironment)
变量环境也是一个词法环境,因此它具有词法环境的所有属性。
ES6规定:词法环境用于const和let绑定,变量环境用于var绑定。
通过一个例子🌰来了解执行上下文的创建:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c= multiply(a, b);
执行上下文如下:
// 全局执行上下文
GlobalExectionContext = {
thisBinding: <Global Object>,
lexicalEnvironment: {
environmentRecord: {
type: "Object",
a: <uninitialized>,
b: <uninitialized>,
multiply: <func>,
},
outer: <null>
},
varibleEnvironment: {
environmentRecord: {
type: "Object",
c: undefined,
},
outer: <null>
},
}
// 函数执行上下文
FunctionExectionContext = {
thisBinding: <Global Object>,
lexicalEnvironment: {
environmentRecord: {
type: "Declarative",
Arguments: {
0: 20,
1: 30,
length: 2,
},
},
outer: <GlobalExectionContext>
},
varibleEnvironment: {
environmentRecord: {
type: "Declarative",
g: undefined
},
outer: <GlobalExectionContext>
}
执行阶段
完成对所有变量的分配,最后执行代码。
在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined
补充:为什么会出现变量提升?
在创建阶段,函数声明存储在词法环境中,而变量会被存储在变量环境中,设置为undefined(在var的情况下),或存储在词法环境中,但保持未初始化(在let和const的情况下)。这就是为什么可以在声明之前访问var定义的变量(尽管是undefined),但如果在声明之前访问let和const定义的变量就会提示引用错误的原因。这就是所谓的变量提升。
总结
在这一节,我们详细介绍了JS中的执行栈和执行上下文,还解释了出现变量提升的原因。接下来我们会继续介绍其他JS核心知识。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。