一文弄懂JS执行上下文

发布于:2024-05-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

执行上下文

执行上下文(Execution context)的概念在 JavaScript 中是颇为重要的。变量或函数的执行上下文决定了它们可以访问哪些数据,以及它们的行为。抽象地讲,可以理解为一个运行环境盒子,JavaScript 代码在这个盒子里运行。

每个执行上下文都有一个关联的变量对象(variable object),这个执行上下文中定义的所有变量和函数都存在于这个对象上,除此之外,还包含作用域链和 this 关键字的值。

JavaScript 中有两种类型的执行上下文:

  • 全局执行上下文(Global Execution Context - GEC)
  • 函数执行上下文(Function Execution Context - FEC)

上下文在其所有代码都执行完毕后被销毁,包括定义在它上面的所有变量和函数(GEC 在应用退出前才会被销毁,比如关闭网页或退出浏览器)。让我们结合如下代码详细了解一下两者。

var a = 10;
function greetings() {
  let word = "hello";
  function addExtra(str1, str2) {
    return str1 + str2;
  }
  return addExtra("hello", "world");
}
greetings();

全局执行上下文

GEC 是基础的默认的执行上下文,执行所有不在函数内部的 JavaScript 代码

根据 ECMAScript 实现的宿主环境,表示全局执行上下文的对象可能不一样。在浏览器中,GEC 就是我们常说的 window 对象,因此所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和方法,而使用 let 和 const 的顶级声明不会定义在 GEC 中。

对于每个JavaScript宿主环境,只能有一个全局上下文。

函数执行上下文

每当函数调用时,JavaScript 引擎都会在全局上下文中创建不同类型的执行上下文,称为函数执行上下文。 由于每个函数都有自己的函数执行上下文(一个函数不一定只有一个,因为)

执行上下文的创建

执行上下文(GEC 或 FEC)的创建分两个阶段进行:

  1. Creation Phase 创建阶段
  2. Execution Phase 执行阶段

Creation Phase 创建阶段

在创建阶段,执行上下文首先与执行上下文对象(Execution Context Object - ECO)相关联。创建阶段又分为3个阶段,在此期间定义和设置执行上下文对象的属性。这些阶段是:

  1. 创建变量对象(Variable Object - VO)
  2. 创建作用域链(Scope Chain)
  3. 设置this关键字的值

让我们详细回顾下每个阶段。

创建变量对象

前文提到,变量对象是在执行上下文中创建的类似对象的容器。它存储在该执行上下文中定义的变量和函数声明。 在 GEC 中,对于使用 var 关键字声明的每个变量,都会向 VO 中添加一个指向该变量并设置为 “undefined” 的属性。

此外,对于每个函数声明,都会向 VO 中添加一个指向该函数的属性,并且该属性存储在内存中。这意味着所有函数声明都将在 VO 中存储和访问,甚至在代码开始运行之前。

因为在代码执行之前就将变量和函数声明存储在内存中,由此引发了提升

我们用伪代码来表示:

GEC = {
  VO: {
    a: undefined,
    greetings: function greetings() { ... }
  }
}

greetingsFEC = {
  VO: {
    word: nothing, // 注意这里不是 undefined
    addExtra: function greetings() { ... }
  }
}

创建作用域链

作用域链是当前作用域中可访问的变量对象列表。作用域中的每个变量对象都表示更高级别的作用域。

用伪代码表示:

GEC = {
  VO: {
    a: undefined,
    greetings: function greetings() { ... }
  },
  scope: [ ...GEC.VO]
}

greetingsFEC = {
  VO: {
    word: nothing, // 注意这里不是 undefined
    addExtra: function greetings() { ... }
  },
  scope: [ GEC.scope, ...greetings.VO]
}

设置 "this"

创建阶段里最后一步是设置 this 关键字的值。Javascript 中 this 关键字是指执行上下文所属的范围。创建作用域链后,JS 引擎将初始化 this 的值。

在 GEC 中,this 指的是全局对象,即 window 对象(浏览器环境)。

对于 FEC,它不会创建 this 对象,相反,它可以访问它所定义的环境。通俗地讲,是指向调用者,因此不会创建对象啦。

Execution Phase 执行阶段

这是实际代码执行开始的阶段。在此之前,VO 中包含值为 undefined 和 nothing 变量,如果运行代码则必然要更新为实际值。然后,代码由解析器解析,转换为可执行的字节码,最后执行。

执行上下文栈

执行上下文栈(Execution Context Stack)跟踪在脚本生命周期内创建的所有执行上下文。因为 JavaScript 是单线程语言,这意味着它一次只能执行一个任务。因此,当其他操作、函数和事件发生时,将为每个事件创建一个执行上下文,再将要其放入执行上下文栈中。如下图所示:

image.png 请注意这个栈中执行顺序,后进先出。

image.png