如果您是JavaScript开发人员或想成为一名JavaScript开发人员,这意味着您需要了解执行JS代码的内部机制。 尤其是,对于掌握其他JavaScript概念(例如引发变量,作用域和闭包),绝对需要了解执行上下文和调用堆栈。 我们今天发布的翻译资料专门针对JavaScript中的执行上下文和调用堆栈。

执行上下文
简单来说,执行上下文是描述执行JavaScript代码的环境的概念。 代码始终在上下文中执行。
▍运行上下文类型
JavaScript具有三种类型的执行上下文:
- 全局执行上下文。 这是基本的默认执行上下文。 如果某些代码不在任何函数中,则此代码属于全局上下文。 全局上下文的特征在于存在一个全局对象,对于浏览器,它是
window
对象,并且this
指向该全局对象。 一个程序只能具有一个全局上下文。 - 函数执行上下文。 每次调用函数时,都会为其创建一个新的上下文。 每个函数都有其自己的执行上下文。 一个程序可能同时具有许多用于执行功能的上下文。 为执行功能创建新的上下文时,它将经历某些步骤序列,我们将在下面讨论。
eval
函数的执行上下文。 在eval
函数内部执行的代码也具有自己的执行上下文。 但是,很少使用eval
函数,因此在这里我们将不讨论该执行上下文。
执行栈
执行堆栈,也称为调用堆栈,是LIFO堆栈,用于存储在代码执行期间创建的执行上下文。
当JS引擎开始处理脚本时,引擎将创建一个全局执行上下文并将其放在当前堆栈中。 当检测到调用函数的命令时,引擎将为此函数创建一个新的执行上下文,并将其放置在堆栈的顶部。
引擎执行一个函数,其执行上下文位于堆栈的顶部。 函数完成后,将从堆栈中删除其上下文,并将控制权转移到堆栈中上一个元素中的上下文。
我们将通过以下示例探索这种想法:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
这是执行此代码后调用堆栈将如何更改的方式。
呼叫堆栈状态将上述代码加载到浏览器中后,JavaScript引擎将创建一个全局执行上下文并将其放在当前调用堆栈中。 调用
first()
函数时,引擎会为此函数创建一个新的上下文并将其放在堆栈的顶部。
当从第
first()
函数调用
first()
函数时,将为此函数创建一个新的执行上下文,并且还将其压入堆栈。
second()
函数完成其工作之后,将其上下文从堆栈中删除,并将控制权转移到位于其下的堆栈上的执行上下文,即
first()
函数的上下文。
当
first()
函数退出时,其上下文将从堆栈中弹出,并将控制权转移到全局上下文。 执行完所有代码后,引擎将从当前堆栈中检索全局执行上下文。
关于创建上下文和运行代码
到目前为止,我们讨论了JS引擎如何管理执行上下文。 现在让我们讨论执行上下文是如何创建的,以及它们创建后会发生什么。 特别是,我们正在谈论创建执行上下文的阶段和代码执行的阶段。
execution创建执行上下文的阶段
在执行JavaScript代码之前,将创建执行上下文。 在创建过程中,将执行三个操作:
- 确定此值并绑定(此绑定)。
- 创建了
LexicalEnvironment
组件。 VariableEnvironment
组件已创建。
从概念上讲,执行上下文可以表示为:
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
此绑定
在全局执行上下文中,
this
包含对全局对象的引用(如上所述,在浏览器中,它是一个
window
对象)。
在函数执行的上下文中,此值取决于函数的调用方式。 如果将其称为对象的方法,则此值绑定到该对象。 在其他情况下,
this
对象绑定到全局对象或设置为
undefined
(在严格模式下)。 考虑一个例子:
let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' 'foo', 'baz' // 'foo' let bar = foo.baz; bar(); // 'this' window, //
词汇环境
根据ES6
规范 ,词法环境是一个术语,用于根据ECMAScript代码的词法嵌套的结构来定义标识符与各个变量和函数之间的关系。 词汇环境由环境记录和对外部词汇环境的引用组成,可以为
null
。
简而言之,词汇环境是一种存储有关标识符和变量的对应关系的信息的结构。 在此,“标识符”是指变量或函数的名称,而“变量”是指对特定对象(包括函数)或原始值的引用。
在词汇环境中,有两个组成部分:
- 环境记录。 这是变量和函数声明的存储位置。
- 链接到外部环境。 此类链接的存在指示词法环境可以访问父词法环境(作用域)。
有两种类型的词汇环境:
- 全局环境(或全局执行上下文)是没有外部环境的词汇环境。 对外部环境的全局环境引用为
null
。 在全局环境中(在环境记录中),可以使用与全局对象相关联的内置语言实体(例如Object
, Array
等),并且用户还定义了全局变量。 在此环境中, this
值指向全局对象。 - 函数的环境,在环境记录中存储了用户声明的变量。 对外部环境的引用既可以指示全局对象,也可以指示所讨论功能之外的功能。
有两种类型的环境记录:
- 声明性环境记录,用于存储变量,函数和参数。
- 环境对象记录,用于在全局上下文中存储有关变量和函数的信息。
结果,在全局环境中,环境记录由对象环境记录表示,而在功能环境中由声明性环境记录表示。
请注意,在函数环境中,环境的声明记录还包含
arguments
对象,该对象存储索引和传递给函数的参数值之间的对应关系,以及有关此类参数数量的信息。
词汇环境可以表示为以下伪代码:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // } outer: <null> } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // } outer: < > } }
环境变量
变量环境也是一个词法环境,其环境记录存储在当前执行上下文中使用
VariableStatement
命令创建的绑定。
由于变量的环境也是词法环境,因此它具有所有上述词法环境的属性。
在ES6中,
LexicalEnvironment
和
VariableEnvironment
组件之间只有一个区别。 它包含以下事实:前者用于存储使用
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 >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // g: undefined }, outer: <GlobalLexicalEnvironment> } }
您可能已经注意到,使用
let
和
const
关键字声明的变量和常量没有关联的值,并且使用
var
关键字声明的变量设置为
undefined
。
之所以如此,是因为在创建上下文期间,代码将搜索变量和函数的声明,而函数的声明则完全存储在环境中。 使用
var
时,变量的值设置为
undefined
,使用
let
或
const
时,变量的值保持未初始化。
这就是为什么您可以在声明之前访问用
var
声明的变量(尽管它们将是
undefined
),但是当您尝试访问在声明之前用
let
和
const
执行的变量或常量时,会发生错误。
我们刚刚描述的被称为“起重变量”。 在执行为变量声明赋值之前,变量声明会“上升”到其词法范围的顶部。
execution代码执行阶段
这也许是本材料中最简单的部分。 在此阶段,将值分配给变量并执行代码。
请注意,如果在执行代码期间JS引擎在声明的位置找不到使用
let
关键字声明的变量的值,它将为该变量分配
undefined
值。
总结
我们刚刚讨论了执行JavaScript代码的内部机制。 尽管要成为一名优秀的JS开发人员,并不一定要了解所有这些知识,但如果您对上述概念有所了解,它将有助于您更好地和更深入地了解该语言的其他机制,例如提高变量,范围,短路。
亲爱的读者们! 您认为除了执行上下文和调用堆栈之外,JavaScript开发人员还知道什么呢?
