定义

是一种规范策略,用来通过ECMAScript编译器追踪代码运行时

简单来说就是代码执行时所在环境的抽象模型,是代码内标识符的集合抽象。

可执行代码(Executable Code)

ECMAScript可执行代码有四种类型

  1. global code:整个js文件。
  2. function code:函数代码。
  3. module:模块代码
  4. eval code:放在eval的代码。

组成部分

执行上下文(Execution Context)由三个组件组成

  • LexicalEnvironment:词法环境组件
  • VariableEnvironment:变量环境组件
    • VariableEnvironment和LexicalEnvironment始终引用词法环境对象
    • 初始化时VariableEnvironment和LexicalEnvironment引用同一个词法环境对象
    • VariableEnvironment的引用不可变,LexicalEnvironment的引用会随着执行发生改变(块级)
  • ThisBinding:this绑定

执行上下文栈(Execution Context Stack)

是一个先进后出的栈式结构(LIFO),用来跟踪维护执行上下文。运行执行上下文(running execution context) 始终位于执行上下文栈的顶层

每当从当前执行代码运行至其他可执行代码时,就会创建新的执行上下文,并将其压入执行栈并成为新的运行执行上下文。当相关代码执行完毕返回后,将正在运行的执行上下文从栈中弹出销毁,之前的执行上下文又成了运行执行上下文。也叫作调用栈。

执行上下文栈工作过程动图演示执行上下文(Execution Context) - 图1

  1. 开始执行任何JS代码前,会先创建全局上下文并压入栈,所以全局上下文一直在栈底
  2. 每次调用函数都会创建新的上下文(在函数内调用自身也是),并压入栈
  3. 函数执行完毕后返回,其执行上下文出栈销毁
  4. 所有代码执行完毕,执行上下文栈只剩全局执行上下文

代码执行

在任意的JavaScript可执行代码被执行时,执行步骤可按如下理解:

  1. 创建一个新的执行上下文(Execution Context)
  2. 创建一个新的词法环境(Lexical Environment)
  3. VariableEnvironmentLexicalEnvironment 都指向新创建的词法环境
  4. 将该执行上下文 推入执行栈 并成为 正在运行的执行上下文
  5. 对代码块内的 标识符进行实例化及初始化
  6. 运行代码
  7. 运行完毕后执行上下文出栈销毁


变量提升(Hoisting)与暂时性死区(temporal dead zone,TD)

变量提升发生在上述步骤的第五步,对代码块内的标识符进行实例化及初始化的具体表现如下:

  1. 执行代码块内的 letconstclass 等声明的标识符合集记录为 lexNames
  2. 执行代码块内的 varfunction 声明的标识符集合记录为 varNames
  3. 如果 lexNames 内的任何标识符在 varNameslexNames 中重复出现,就会报错SyntaxError
    • 这就是为什么var和function可以重复声明,而let、const、class等不行
  4. varNamesvar 声明的标识符实例化并初始化赋值undefined,同名的只操作最后一个
  5. lexNames 中的标识符实例化,但不会进行初始化(即依旧会提升,但无法访问)
    1. 在运行至其声明处代码时才会进行初始化,在初始化之前访问都会报错
    2. 这就是暂时性死区,let、const和class声明的变量其实提升了,只是没有被初始化,初始化之前不可访问
  6. 最后将 varNames 内的函数声明实例化,并初始化赋值对应的函数体

为什么会有两个环境组件

首先声明组件的作用

  • VariableEnvironment:记录 var 和 function 声明的标识符
  • LexicalEnvironment:记录其他声明的绑定,如let、const、class等

一般情况下 Exexution Contexts 中的 VariableEnvironmentLexicalEnvironment 指向同一个词法环境,之所以区分两个组件,是为了实现块级作用域的同时不影响var及函数声明

块级作用域实现方式

  1. 一个正在执行的上下文中,VariableEnvironmentLexicalEnvironment 指向同一个词法环境,记录了所有的变量声明
  2. 当执行到块级代码时,会将 LexicalEnvironment 记录下来,记录为 oldEnv
  3. 然后创建一个新的 LexicalEnvironment 和词法环境(外部引用 outer 指向 oldEnv),记录为 newEnv
  4. newEnv 设置为正在执行上下文的 LexicalEnvironment
  5. 块级代码内的 letconst 等会绑定在 newEnv
  6. varfunction 还是绑定在原本的 VariableEnvironment 上面
    • 块级代码内的函数会被当做var声明,会被提升至外部环境,块级代码运行前其值为初始值 undefined
  7. 块级代码执行完毕后,又将 oldEnv 还原正在执行上下文的 lexicalEnvironment

目前包括块级代码(在一对大括号内的代码)、for、switch、TryCatch语句中的catch从句及with语句(with语句创建的新环境为对象式环境,其他皆为声明式环境)都是这样来实现块级作用域的