JavaScript 执行上下文:深入探究
执行上下文的真实示例
假设你是一家餐厅的经理,你的餐厅就像一个 JavaScript 程序。餐厅的每个部分都代表一个执行上下文。
餐厅的第一部分是餐厅,它类似于 JavaScript 中的全局执行上下文。这是顾客用餐的主要区域。它包含餐厅顺利运营所需的一切,例如桌椅、菜单和服务员。这类似于全局执行上下文包含 JavaScript 程序运行所需的所有变量、函数和对象。
现在,假设一位顾客想要点餐。顾客就像用户输入,而接受订单的行为就像创建一个新的执行上下文。服务员接过顾客的订单并移至厨房区域,这就像在 JavaScript 中创建一个新的执行上下文。
在厨房区域,厨师和厨房工作人员根据服务员的订单准备餐点。这个区域就像一个新的执行上下文,类似于 JavaScript 中的每个函数调用都会创建一个新的执行上下文。厨房有自己的一套工具和配料,就像 JavaScript 中的每个执行上下文都有自己的一套变量和函数一样。
餐点做好后,就会送到餐厅的顾客手中。这就像函数调用返回到全局执行上下文一样。顾客收到餐点,餐厅继续顺利运营。
总的来说,就像餐厅的运营方式一样,不同的区域提供不同的功能,JavaScript 代码在程序中移动时也会在不同的执行上下文中运行。
JavaScript
JavaScript 是一种用于 Web 开发的高级编程语言。它是单线程的,这意味着它一次只能执行一个命令,并且是解释型的,也就是说它在执行之前不会被编译。
V8 是由 Google 开发的高性能 JavaScript 引擎,用于其 Chrome 浏览器以及其他应用程序。它将 JavaScript 代码编译为机器码,从而提高其性能。
JavaScript 主要用于客户端脚本,这意味着它在用户的 Web 浏览器上运行,用于创建动态且交互式的网页。它通常与 HTML 和 CSS 结合使用。
执行上下文
JavaScript 执行上下文是 JavaScript 代码执行的环境。它包含正在执行的代码可用的变量、函数和对象的信息,以及作用域链和 this 关键字的值。
执行上下文有两个阶段:
- 创建阶段
- 执行阶段
创建阶段:
在此阶段,JavaScript 引擎设置要执行的代码的环境。在此阶段,JavaScript 引擎会创建以下内容:
- 变量对象 (VO): VO 包含当前作用域内定义的所有变量和函数。这包括函数参数、函数声明和变量声明。VO 用于在执行期间将标识符解析为其值。
- 作用域链:作用域链是当前作用域内可访问的变量对象列表。作用域链中的每个变量对象都代表一个更高级别的作用域。
- “this”关键字: “this”关键字设置为“this”对象的值。
执行阶段:
在此阶段,JavaScript 引擎逐行执行代码。JavaScript 引擎读取代码并逐行执行。此阶段涉及以下步骤:
- 为变量赋值:在执行阶段,JavaScript 引擎会为变量赋值。如果变量未初始化,则其值为“undefined”。
- 执行函数和代码块: JavaScript 引擎在代码中遇到函数和代码块时会执行它们。如果调用某个函数,引擎会为该函数创建一个新的执行上下文,并将其添加到调用堆栈中。
- 管理调用栈:调用栈是一种数据结构,用于跟踪正在执行的函数。当一个函数被调用时,它的执行上下文会被添加到调用栈的顶部。当函数返回时,它的执行上下文会被从调用栈中移除。
JavaScript 中执行上下文的两个主要组成部分是:
- 内存组件:指为上下文中的代码和数据组件分配的内存空间。这包括代码使用或操作的变量、对象、数组和其他数据结构。内存组件还负责维护作用域链,作用域链是函数可以访问的变量对象的列表,从函数自身的变量对象开始,到其父函数的变量对象,一直到全局变量对象。
- 代码组件:指在上下文中执行的实际代码。它包括所有函数和变量声明,以及构成代码的任何其他指令。在执行上下文的创建阶段,JavaScript 引擎会通过称为“提升”的过程为所有变量和函数声明设置内存空间。
这两个组件共同作用,使 JavaScript 引擎能够执行代码并管理程序中的数据。了解这些组件的工作原理对于编写高效的 JavaScript 代码至关重要。
让我们举个例子:
function greetings() {
console.log("Welcome to the JS world!");
}
greetings();
var number1 = 10;
var number2 = 5;
function add(number1, number2) {
return number1 + number2;
}
function addExtra(number1, number2) {
var extra = 15;
return number1 + number2 + extra;
}
var result1 = add(number1, number2);
var result2 = addExtra(number1, number2);
console.log(result1);
console.log(result2);
最初调用堆栈是空的
JavaScript 引擎首先运行完整的源代码,然后执行以下操作:
- 创建全局执行上下文(GEC)并将其推送到调用堆栈。
- 在浏览器中创建一个全局对象窗口,在 NodeJs 中创建一个全局对象。
- 将值未定义的变量和函数体引用存储起来。
将全局执行上下文推送到调用堆栈。
全局执行上下文的内存创建
在内存创建阶段之后,执行上下文将进入代码执行阶段。
该greetings
函数被调用。
创建问候执行上下文并将其推送到调用堆栈。
问候语执行上下文的内存创建和代码执行。这里greetings
调用了该函数,因此它会执行并将字符串“Welcome to the JS world!”打印到控制台。
安慰
从调用堆栈中弹出问候执行上下文。
变量number1
定义并赋值 10
变量number2
定义并赋值 5
add
调用一个函数,该函数接受两个参数number1 = 10
和number2 = 5
。在函数内部,将两个参数相加,并result = 15
返回 。
创建添加执行上下文并将其推送到调用堆栈。
添加执行上下文的内存创建和代码执行。这里返回了 和 的number1
和number2
。
变量result1
定义并分配调用的结果add
。
从调用堆栈中弹出添加执行上下文。
addExtra
调用一个函数,该函数接受两个参数number1
和number2
。在函数内部,extra
定义一个新变量并将其赋值为 15。然后,返回number1
、number2
和extra
的和。
创建 addExtra 执行上下文并将其推送到调用堆栈。
addExtra 执行上下文的内存创建和代码执行。这里extra
定义了 ,并赋值为 15,返回number1
、number2
和 的和extra
。
变量result2
定义并分配调用的结果addExtra
。
从调用堆栈中弹出 addExtra 执行上下文。
的值result1 = 15
记录到控制台。
安慰
的值result2 = 30
记录到控制台。
安慰
从调用堆栈中弹出全局执行上下文。
结论
JavaScript 执行上下文是一个基本概念,它定义了 JavaScript 代码的执行环境。它在 JavaScript 的运行方式以及与周围环境的交互中起着至关重要的作用,理解它对于编写高效的 JavaScript 代码至关重要。执行上下文是一种数据结构,其中包含有关 JavaScript 代码执行环境的信息。
每个执行上下文都有一个作用域链,它决定了该上下文可以访问哪些变量和函数。作用域链由执行上下文的词法环境决定,用于在代码执行时查找变量和函数。
变量对象是执行上下文的另一个重要组成部分。它是一种数据结构,包含执行上下文中定义的变量、函数和参数的信息。变量对象用于存储和管理上下文中的变量和函数,并会随着代码的执行而动态更新。
this 关键字也是执行上下文的重要组成部分。this 的值由函数调用的上下文决定。在全局执行上下文中,this 指向全局对象。在函数执行上下文中,this 指向该函数所属方法的对象;如果该函数不是任何对象的方法,则指向全局对象。
提升是另一个与执行上下文密切相关的概念。在 JavaScript 中,变量和函数声明会被提升到其各自执行上下文的顶部。这意味着它们可以在代码中声明之前使用。
最后,执行栈是一种数据结构,它以后进先出 (LIFO) 的顺序存储执行上下文。当一个函数被调用时,会创建一个新的执行上下文并将其压入栈顶。当一个函数返回时,它的执行上下文会被弹出栈顶。
执行上下文是 JavaScript 中的一个基本概念,它定义了代码的执行环境。它包含该环境中定义的变量、函数和参数的信息,以及作用域链和 this 关键字。通过理解执行上下文,开发者可以编写更高效的 JavaScript 代码,最终实现更高性能和更可靠的应用程序。
文章来源:https://dev.to/jahid6597/javascript-execution-context-a-deep-dive-4kno