JS 执行上下文

359 阅读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"

代码说明了上面的 2let 声明的变量不会在全局对象上,而是在 全局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. 创建完成,开始执行代码