😭 我终于弄懂 JS 执行上下文!!!

先理解一段概念

什么是 Execution Context?

打个比方:你要去参与一个舞蹈演出,是不是首先你得有一个演出的场地去供你去演出?

所以说JavaScript (你)在执行语句前 (在舞蹈演出前),需要经过的一系列的"准备",为代码执行创造的执行环境就是执行上下文(需要演出场地环境)。

什么是文本环境?

每个演出是不是都有参演人员名单?指导老师可以通过参演人员名单去安排每个参演人员需要干什么。

那么类似的,文本环境(Lexical Environment) 就相当于演出中的参演人员名单,用于在 JS 代码执行之前把变量名、类名、函数名、......等等登记在文本环境上。JS 在执行过程中就可以在文本环境中查找变量、函数还有类等所需的东西。

什么是执行栈?

我们要找变量的前提是找到文本环境,而文本环境在执行上下文中,那么 JS 在哪里找到执行上下文呢?

JS 在 执行栈(Execution Context Stack) 中找执行上下文:

  • 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

  • 引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程会到达当前栈中的下一个上下文。

  • 执行栈栈顶的执行上下文称为当前执行上下文。

  • JS 代码总是在当前上下文中运行(也就是说 JS 代码中所需要用到的资源是到当前执行上下文中查找)。

什么时候会创建新的执行上下文?

3种情况下会创建新的执行上下文

  • 进入全局代码
  • 进入 Function 函数体代码
  • 进入 Eval 函数参数指定的代码

小结

  1. 执行上下文 是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

  2. 文本环境 是 JavaScript 引擎用于存储变量和函数的容器。

  3. 执行栈 是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

  4. JavaScript 中有三种执行上下文类型。

    • 全局执行上下文 :任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值指向这个全局对象。一个程序中只会有一个全局执行上下文。

    • 函数执行上下文: 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。

    • Eval 函数执行上下文: 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以不讨论它。

怎么创建执行上下文?

我们先来一个例子,解释说明一下大概流程:


Step1:创建执行上下文,加入栈顶

首先会创建全局执行上下文,加入栈顶。它的文本环境由两部分组成:第一部分是全局对象(在浏览器环境下,就是表示 Windows 对象),第二部分是全局 Scope。

有两个特殊的地方:

  • var 和 function 声明创建在全局对象中,而 let 、const 、class 声明的变量创建在全局 Scope 中。

  • 在执行过程中,需要寻找变量,首先先到全局 Scope 中找,找不到再到全局对象中找。

举些例子理解:

js 复制代码
// let 声明的变量创建在全局 Scope 中
// 所以 window 上面没有 aLet 会输出 undefined
let aLet = 'aLet';
console.log(aLet); // aLet
console.log(window.aLet); // undefined
js 复制代码
// var 声明创建在全局对象
// 需要寻找变量时,首先先到全局 Scope 中找,找不到再到全局对象中找。
// 所以这里都有输出
var aVar = "aVar";
console.log(aVar); // aVar
console.log(window.aVar); // aVar

Step2:分析

  • 找到所有的非函数中的var声明。

  • 找到所有的顶级函数声明(顶级函数声明就是不包括在大括号内的函数声明)

  • 找到顶级let const classj声明

  • 找到块中声明的,函数名不与上述重复(中)

Step3:名字重复处理

注意:

  • 1et const class 声明的名字之间不能重复

  • let const class 和 var function 的名字不能重复

  • var 和 Function 名字重复的,function 声明的函数名优先

Step4:创建绑定,登记到全局上下文里面

  • var 登记并初始化为 undefined。

  • 顶级函数声明:登记function名字,并初始化为新创建函数对象。

  • 块级中函数声明:登记函数名字,初始化为undefined。

  • let const class 登记但未初始化。

这里就可以解释为什么 var声明的变量 会有变量提升的情况,而 let const class 声明的变量没有变量提升的情况。这是因为 let const class 声明的变量在登记的时候就没有初始化,是不可以使用的!

Step5: 执行代码

最后就是可以开始执行代码了。根据代码的执行顺序,逐行执行,并根据文本环境进行变量的查找和赋值等等操作。


那么最开始的那个例子会在全局上下文中登记 var 声明的变量 a 并初始化为 undefined,然后开始执行语句,所以输出为 undefined 而不是 'foo'。

js 复制代码
console.log(a); // undefined
if (false) {
  var a = "foo";
}

然后需要注意的是:

  • 每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

  • 函数对象在被创建的时候,函数对象的体内会保存函数创建时的执行上下文的文本环境。

  • 函数的执行上下文的文本环境没有全局对象只有函数 Scope。函数里面的变量,不管 let const class var 还是 函数声明 都是创建在这个函数 Scope 里面。

举个例子:

这个例子在创建 foo 函数的执行上下文的时候,里面的文本环境就只有一个 a 变量,但是因为是 let 声明的,所以未被初始化,在执行 foo 函数时就会报错。

ini 复制代码
var a = 10;
function foo() {
  console.log(a);
  let a;
}
foo(); // 会报错 ReferenceError: Cannot access 'a' before initialization

小结

创建执行上下文的过程可以分为以下几个步骤:

  1. 创建执行上下文:在代码执行之前,会先创建全局执行上下文,将其加入执行上下文栈的栈顶。全局执行上下文的文本环境由两部分组成:全局对象(在浏览器环境下,表示为 window 对象)和全局作用域。然后也会创建函数执行上下文:函数对象在被创建时,会保存函数创建时的执行上下文的文本环境。函数的执行上下文的文本环境没有全局对象,只有函数作用域。

  2. 分析变量声明:在创建执行上下文时,会先找到所有非函数中的 var 声明,然后找到顶级函数声明(不包括在大括号内的函数声明)。接着找到顶级的 letconstclass 声明。最后,在块级作用域中找到函数声明。

  3. 处理名字重复:在执行上下文中,需要处理变量名字的重复情况。letconstclass 声明的名字之间不能重复,letconstclass 声明的名字与 var 和函数声明的名字也不能重复。如果出现了重复的情况,会按照一定的优先级规则进行处理。

  4. 创建绑定并登记到执行上下文中:在处理完变量声明之后,会创建绑定并将其登记到执行上下文中。对于 var 声明的变量,会进行登记并初始化为 undefined。对于顶级函数声明,会登记函数名字并初始化为新创建的函数对象。对于块级中的函数声明,会登记函数名字但不进行初始化。而对于 letconstclass 声明的变量,会进行登记但不进行初始化。需要注意的是,函数内部的变量,无论是使用 letconstclassvar 还是函数声明,都是在函数作用域中创建并初始化的。

  5. 执行代码:在执行上下文创建完毕后,就可以开始执行代码了。根据代码的执行顺序,逐行执行,并根据作用域链和词法环境进行变量的查找和赋值操作。

后记

😖我也是看了很多文章才慢慢对 JS 执行上下文有些理解,然后我推荐一篇关于JS执行上下文的文章([译] 理解 JavaScript 中的执行上下文和执行栈)这篇讲的很专业,但是就是有点晦涩。我看的时候也是半懵半懂。最后的最后,希望本文能增加大家对 Js 执行上下文的理解。本人水平有限,如果发现问题或者需要补充的点欢迎大家通过评论告诉我!!!

相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606110 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅11 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment11 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax