JS 执行上下文

384 阅读5分钟

Execute Context

程序本质上是一个状态机,所谓的状态就是 变量,js 代码在执行的过程中,如何 存储状态 读取状态 , 改变状态 ?

☘️ 什么是上下文?

上:之前执行过程中,形成的接下来可能需要用的状态

下:接下来执行可能需要用到的状态

js在执行语句之前,为代码执行提供 查找状态 的地方,就是所谓的上下文

为什么需要上下文?

程序执行时, 查找变量需要依赖上下文

上下文中有哪些内容?

  • 文本环境 (Lexical Environment)(词法环境)
  • this Binding

js 通过文本环境查找需要的变量

☘️ 上下文在什么地方?JS如何管理上下文?

答案是 执行上下文栈 (Excution Context Stack)

  1. JS每次创建一个新的 上下文,就会把他放在 ECS的栈顶
  2. JS在执行的过程中,总是在执行栈的栈顶的上下文(当前执行上下文)中查找变量
  3. 当一个函数执行完,其依赖的上下文弹出栈顶
  4. 当整个页面(程序)结束,全局上下文弹出

☘️ 什么情况会创建上下文?

目前有四种情况会创建上下文:

  1. 进入全局代码
  2. 函数执行前
  3. 执行 eval 指定的代码
  4. 进入 module 代码前

☘️ 上下文的创建过程?

全局上下文和函数的上下文有所不同,本篇主要讨论 全局上下文 ,目的是对上下文的创建过程产生认识,而函数的上下文创建将放在下节 函数的作用域 讲解。

Step1

第一步是在 执行上下文中栈中压入 全局执行上下文

image.png

可以看到, 全局上下文 由两部分组成

  • This Binding
  • Lexical Environment

其中, This Binding 指向 全局对象 , 在浏览器环境下是 window 对象

全局上下文有一个很特殊的地方,他的 Lexical Environment (文本环境)由两部分组成

  • 全局对象 (window)
  • 全局scope (后面会解释它的作用)

step2

第二部是处理声明

  • 找到所有的 非函数 中的 var 声明
  • 找到所有 顶级函数声明 (不是函数表达式)
  • 找到 顶级 let const class 声明

解释: 顶级 是指该行代码以 声明开头,前面没有其他符号

找到后干嘛呢? 放入Lexical Environment

⚠️ 核心:

全局的代码中:

  1. var function 声明的变量, 创建在 全局对象 中
  2. let const class 声明的变量创建在 全局scope 中
  3. 查找变量时,先在 全局scope 查找, 找不到则在 全局对象 中查找

代码验证:

let demo = "demo";
console.log(demo); //"demo"
console.log(window.demo); //"undefined"

代码说明了上面的 2 , let 声明的变量不会在全局对象上,而是在 全局scope 中

var demo = "demo"
console.log(demo); //"demo"
console.log(window.demo); //"demo"

代码说明了上面的 1

step3

第三步是 检查重复定义

分两步:

  • 全局scope 中是否有重复声明, 有则 报错
  • 全局 scope 中是否有与 全局对象 重复声明的变量, 有则 报错

这一步是 let const class 声明的变量 不能重复定义

step4

第四步是创建绑定,也就是为上下文中的变量 初步赋值

全局对象中的变量:

  1. var 声明的变量 赋值为 undefined
  2. 函数声明会直接 创建函数对象 , 然后指向该对象

⚠️ 注意:

  1. 执行顺序如上, 因此函数声明 可能会覆盖 var 声明

  2. 函数对象会直接创建,函数对象创建时,会在 ‘体内’([[scope]]属性) 保存函数创建时执行上下文的文本环境:

image.png

这里也解释了,为什么说 JS是 词法环境 ,是因为函数的作用域在其声明的时候就已经确定,而不是在执行的时候确定

全局scope中的变量:

不会进行初始化

⚠️ TDZ:暂时性死区

scope中的变量不会进行初始化,js规定,对未初始化的变量进行引用, 会报错,也就是所谓的暂时性死区。

step5

至此,执行上下文已经生成完毕,可以开始执行代码

outer

前面忽略了一个细节,文本环境中,除了记录变量查找,还有一个 outer 记录,该值指向了当前上下文外部的一个上下文,在全局上下文中,outer的值比较特殊,为 null

总结:

上下文是函数或者全局执行前的环境,是代码执行时查找变量和this的地方,js通过 ECS执行上下文栈

来管理上下文,新创建的上下文位于栈顶,js执行时总是通过栈顶的上下文查找需要的状态,因此栈顶的上下文称为 当前执行上下文 current execute context

第一行js代码开始解析时,要创建全局上下文

全局上下文的创建步骤如下:

  1. 将全局上下文压入 ECS
  2. 将 var 和 顶级函数声明 放入 全局对象(浏览器window)
  3. 将 顶级 let const class 声明放入 全局scope
  4. 全局scope 变量声明查重,如果在 全局对象 或者 scope 中有重复的声明就报错
  5. 为 var 声明变量赋值 undefined
  6. 为 顶级函数声明 赋值 函数对象 ,该对象内部会记录 当前上下文的 Lexcial Environment
  7. 创建完成,开始执行代码