You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
leta='Hello World!';functionfirst(){console.log('Inside first function');second();console.log('Again inside first function');}functionsecond(){console.log('Inside second function');}first();console.log('Inside Global Execution Context');
如果你是一名 JavaScript 开发者,或者想要成为一名 JavaScript 开发者,那么你必须知道 JavaScript 程序内部的执行机制。理解执行上下文和执行栈同样有助于理解其他的 JavaScript 概念如变量提升机制、作用域和闭包等。
正确理解执行上下文和执行栈的概念将有助于你成为一名更好的 JavaScript 开发人员。
所以不用多说了,让我们开始吧!
1 什么是执行上下文( Execution Context)
简单来说,执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript中运行的所有的代码都是在执行上下文中运行。
1.1 执行上下文的类型
执行上下文总共有三种类型
全局执行上下文: 这是默认的、最基础的执行上下文。对于不在任何函数中的代码都位于全局执行上下文中。它做了两件事
this
指针指向这个全局对象。一个对象只能存在一个全局执行上下文
函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤
Eval函数执行上下文: 运行在
eval
函数中的代码也获得了自己的执行上下文,但由于JavaScript开发人员通常不使用eval
,因此我不在文中讨论2 执行栈(Execution Stack)
执行栈,在其他语言中也会称之为调用栈,具有LIFO(后进先出)结构,用于存储代码执行期间创建的所有执行上下文
每当 JavaScript 引擎首次读取你的JavaScript代码时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当JavaScript引擎找到一个函数调用,引擎都会为该函数创建一个新的执行上下文并将其推到当前执行栈的顶部
引擎会先运行其执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文
让我们通过下面的代码示例来理解这一点:
上述代码的执行上下文栈。
当上述代码在浏览器中加载时,Javascript引擎会创建一个全局执行上下文并将其推送到当前执行栈。当开始对
first()
的调用时,Javascript引擎为该函数创建一个新的执行上下文,并将其推送到当前执行栈的顶部。当从
first()
函数中调用second()
函数时,Javascript引擎为该函数创建一个新的执行上下文,并将其推送到当前执行栈的顶部。当second()
函数执行完成后,它的执行上下文从当前执行栈栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文,即first
函数执行上下文当
first()
完成时,其执行栈将从栈中移除,并且上下文控制权将移至全局执行上下文。一旦执行完所有代码,JavaScript引擎就会从当前栈中删除全局执行上下文。3 执行上下文是如何被创建的
到目前为止,我们已经看到 JavaScript 引擎如何管理执行上下文。现在让我们理解 JavaScript 引擎如何创建执行上下文。
执行上下文分两个阶段创建:1)创建阶段、2)执行阶段。
3.1 创建阶段
在任意的 JavaScript 代码被执行前,执行上下文处于创建阶段。在创建阶段总共发生了三件事
因此执行上下文可以在概念上表示为:
3.1.1 This Binding
在全局执行上下文中,
this
的值指向全局对象。(在浏览器中,this
的值指向 window对象)在函数执行上下文中,
this
的值取决于函数的调用方式。如果它被一个对象引用调用,那么this
的值被设置为该对象,否则将其值设置为全局对象或未定义(在严格模式下)。例如:
3.1.2 词法环境(Lexical Environment)
官方ES6 文档将词法环境定义为:
简而言之,词法环境是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用)
在词法环境中,有两个组成部分:(1)环境记录(environment record) (2)对外部词法环境的引用
词法环境有两种类型:
this
的值指向这个全局对象。环境记录
中。对外部词法环境的引用可以是全局,也可以是包含内部函数的外部函数环境。注 -对于函数环境而言,环境记录 还包含了一个
arguments
对象,该对象包含了索引和传递给函数的参数之间的映射以及传递给函数的参数的长度(数量)。例如,以下函数的参数对象如下所示:
环境记录(environment record) 同样有两种类型(如下所示)
抽象来讲,我们可以写出下面的伪代码来表示不同类型下的词法环境
3.1.3 对象环境(variable environment)
它也是一个词法环境,其
EnvironmentRecord
包含了由VariableStatements(变量声明)
在此执行上下文创建的绑定。如上所述,变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。
在ES6中,LexicalEnvironment 和 VariableEnvironment 组件的区别在于前者用于存储函数声明和变量(
let
和const
)的绑定,后者仅仅用于存储变量(var
)绑定结合实际代码可以更清晰理解概念:
伪代码的执行上下文如下所示:
注 -只有在
multiply
函数调用的时候才会创建函数的执行上下文你可能已经注意到了
let
和const
定义的变量没有任何与之关联的值,此时为未初始化状态,但var
定义的变量设置为undefined
。这是因为在创建阶段,代码会被扫描并解析变量和函数声明,其中函数声明存储在环境中,而变量会被设置为
undefined
(在var
的情况下)或保持未初始化(在let
和const
的情况下)。这就是为什么你可以在声明之前访问
var
定义的变量(尽管是undefined
),但如果在声明之前访问let
和const
定义的变量就会提示引用错误的原因。这就是我们所谓的变量提升。
4.执行上下文执行阶段
这是整篇文章中最简单的部分。在此阶段,完成对所有变量的分配,最后执行代码。
注: 在执行阶段,如果 Javascript 引擎在源代码中声明的实际位置找不到 let 变量的值,那么将为其分配 undefined 值。
5.总结
我们已经讨论了 JavaScript 内部是如何执行的。虽然你没有必要学习这些所有的概念从而成为一名出色的 JavaScript 开发人员,但对上述概念的理解将有助于你更轻松、更深入地理解其他概念,如变量提升、域和闭包等。
译者注:
The text was updated successfully, but these errors were encountered: