今天这篇文章,我们将了解 JavaScript 提供的黑盒,让我们的代码神奇地运行“执行上下文”。
这是迄今为止最重要的主题之一,它可以使你对其他关键主题一目了然,例如,作用域、词法作用域、闭包和提升,而且学习JavaScript的真正工作原理很有趣。
到目前为止,在代码编辑器(Vs code )中编写的每一行混乱代码都在我们现在将讨论的这个执行上下文中运行。
坐下来,放松一下,收拾好你的美食,因为我会让你明白的。
在 JavaScript 中,一切都发生在执行上下文中,我的意思是一切。你可以将其视为评估和执行 JavaScript 代码的环境。
每当你的浏览器与任何 JavaScript 代码交叉路径时,浏览器的 JavaScript 引擎就会创建一个特殊的环境来处理此 JavaScript 代码的转换和执行。这个环境被称为执行上下文。
执行上下文包含当前正在运行的代码以及有助于其执行的所有内容。
执行上下文的类型
当你在浏览器中运行脚本时,javascript 引擎会创建不同类型的执行上下文。
全局执行上下文 (GEC)
当你第一次运行脚本或你的代码不在任何函数中时,它会被放置在全局执行上下文 (GEC) 中。
在这里,每当 JavaScript 引擎接收到一个脚本文件时,它首先会创建一个默认执行上下文,这就是我们所说的全局执行上下文 (GEC)。它是一个基本/默认执行上下文,所有不在函数内部的代码都会在其中执行。
注意:每个 JavaScript文件只有一个 GEC
函数执行上下文 (FEC)
每当你的 JavaScript 引擎遇到函数调用时,它都会在全局执行上下文中创建一种称为函数执行上下文的不同类型的 EC,以评估和执行该函数内部编写的代码。
每个函数调用都有自己的 FEC(即使你多次调用同一个函数),因此,在脚本运行时可以有多个 FEC。
它们是如何创建的?
现在执行上下文的创建分两个阶段进行:
创建阶段
执行阶段
1、创建阶段
在此阶段,将创建一个执行上下文对象 (ECO),其中包含我们的代码在其运行时(执行阶段)使用的重要信息/数据。
属性在此对象 (ECO) 中分三个不同阶段进行设置和定义。
创建变量对象 (VO)。
创建范围链。
赋予此关键字价值。
阶段 1:变量对象的创建
变量对象就像一个在执行上下文中创建的容器,它将变量和函数声明存储在键:值对(不是函数表达式)中。
在 GEC 中,使用 var 关键字声明的每个变量都会向指向该变量的变量对象添加一个属性,并将其值设置为未定义,使用 let 或 const 声明的变量获取未初始化的值,而在函数声明中,一个属性被添加到指向该函数的变量对象中,所有的函数声明都将被存储并可以在 VO 中访问,甚至在代码开始运行之前。
在 FEC 中,不会创建此变量对象,而是构造了一个名为“argument”的类似数组的对象,其中包括提供给该函数的所有参数。
这种甚至在代码执行之前就将变量和函数(声明)存储在内存中,这就是我们所说的提升。
第 2 阶段:创建范围链
在 JavaScript 中,作用域是一种了解一段代码对脚本其他域的可访问性的方法。
每个函数执行上下文都会创建它的作用域,可以将其视为一个环境或空间,它定义的变量和函数可以通过一个称为作用域的进程来访问。现在,当一个函数(比如 X() )在另一个函数(比如 Y() )中定义时,这个内部函数 X() 将可以访问变量,并且在外部函数 Y() 中定义的其他函数也将具有访问外部函数的代码,但事情并不止于此,它还可以访问其父元素的代码等等,直到 GCE,这种行为就是我们所说的词法作用域,但反过来不是真的。
这个作用域的概念在JavaScript 中引发了一个被称为闭包的相关现象,即使在外部函数执行完成之后,内部函数也可以访问与外部函数关联的代码……它已经死了,消失了,很久了走了。
让我们再看一个例子来理解作用域链。
这里变量 a 和 b 没有在函数 second() 中定义,它只能访问在其自己的范围(本地范围)中定义的变量 c,但是由于词法范围,它可以访问它所在的函数以及它的父母。因此,当你运行此代码时,JavaScript 引擎将无法找到变量 a 或 b,因此,它将沿着执行上下文链首先找到 b 并解析它,因为它已在函数 first() 范围内成功找到它,解析后继续查找变量 a ,JavaScript 引擎为该变量一直到全局执行上下文并解析它。
这个 JavaScript 引擎沿着不同执行上下文链向上的过程,或者我们可以说遍历执行上下文的范围以解析变量或函数调用/调用,称为 Scope Chaining。
第 3 阶段:设置“this”关键字的值
在 javascript 中,this 关键字是指执行上下文所属的范围。
在 GEC 中,这指的是一个全局对象,在浏览器的情况下是一个窗口对象。因此,在函数声明中,使用“var”关键字初始化的变量分别作为方法和属性分配给这个全局对象。
所以
与以下内容相同
但在 FEC 的情况下,它不会创建“this”关键字,而是可以访问定义它的环境的关键字。
执行阶段
在执行上下文的这个阶段,我们的代码开始执行,执行后从执行堆栈或调用堆栈弹出,我们将在本文后面介绍。
到目前为止,Variable 对象包含值为 undefined 和 uninitialized 的变量,具体取决于变量是分别使用 var 关键字还是使用 let/const 声明的。
这里 JavaScript引擎再次读取 EC 中的代码,用它们的实际值更新这些变量。然后代码被解析,被转译,最后被执行
执行堆栈(调用堆栈)
你有没有想过 JavaScript 引擎如何跟踪它在脚本运行时创建的各种 EC 的所有这些创建和删除?答案是执行堆栈或简单的调用堆栈。
“JavaScript 是一种同步的单线程语言”
单线程是指它只能够一次执行一个任务,一次是一行代码,而同步是指这些任务的执行以特定的顺序发生。因此,当 JavaScript 引擎读取脚本时,它会创建不同的执行上下文并将它们存储在称为调用堆栈或执行堆栈的堆栈数据结构中。
当脚本在浏览器中加载时,浏览器的 JS 引擎首先会创建一个我们在上面详细介绍过的默认特殊环境,即全局执行上下文,并将其推送到此执行堆栈。
之后,当 JS 引擎发现函数调用时执行文件时,它会为其创建一个单独的函数执行上下文,如下图所示(步骤 2),并将其推送到现有默认 GEC 之上的堆栈中。
在执行 firstFunc() 时,它遇到对 secondFunc() 的调用,它暂停 firstFunc() 的执行并创建另一个 FEC 并推送到 firstFunc() FEC 顶部的堆栈,然后再次为 thirdFunc() 创建一个单独的 FEC 称呼。
顶部的 EC 将首先由 JS 引擎执行,执行完成后,它会从堆栈中弹出,并开始执行上一个活动 EC 下面的 EC,如上图所示,直到到达 GEC。
结论
执行上下文是 JavaScript 的核心,理解它很重要,因为它可以帮助你正确理解其他主要概念。