JS执行上下文

135 阅读7分钟

这是一篇笔记,记录在学习执行上下文时补充的一些知识点,方便日后回忆

JavaScript 中有三种执行上下文类型。

  • 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  • Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

ES3 中的执行上下文:

  • 类型:
    • 全局执行上下文:整个程序中只有一个,在脚本生命周期内存在于执行堆栈底部。会生成全局对象(如浏览器中的 window),并将 this 绑定到全局对象。
    • 函数执行上下文:每当函数被调用时创建。
    • Eval 函数执行上下文:由于不常用,暂不分析。
  • 内容:
    • 变量对象(VO):每个执行环境都有,全局执行环境的变量对象始终存在,函数执行环境的变量对象在函数调用且执行代码前,用参数列表初始化,函数声明变量声明会作为属性添加。只有函数声明会被加入,函数表达式会被忽略。
    • 活动对象(AO):函数进入执行阶段,变量对象被激活成为活动对象,此时可访问其中的属性。
    • 作用域链:规定变量方式,从当前上下文变量对象开始,若未找到则从父级查找,直到全局对象。函数创建时确定作用域,执行时构建作用域链。
    • 当前可执行代码块的调用者(this`):根据调用方式确定,如对象方法调用、使用特定 API 委托调用等,否则默认为全局对象调用。
  • 生命周期:
    • 创建阶段:函数调用时且执行函数体内前,用参数初始化变量对象,进行变量和函数的初始化声明(变量声明提升),构建作用域链,确定 this 值。
    • 执行阶段:逐条执行代码,对变量赋值,访问变量,函数调用创建新执行上下文等。
    • 销毁阶段:一般函数执行完,局部执行上下文弹出销毁,闭包情况特殊,会导致函数变量对象驻存内存,直到闭包引用销毁。

ES5 中的执行上下文:

  • 执行上下文的组成:

    • ThisBinding:表示 this 的值。
    • LexicalEnvironment:词法环境组件,类似于 ES3 中的变量对象,用于存储变量和函数的映射关系。
    • VariableEnvironment:变量环境组件,为 ES6 服务,处理 var 变量绑定。
  • 执行过程:

全局上下文被创建

    1. 创建全局上下文的 词法环境

      1. 创建 对象环境记录器 ,它用来定义出现在 全局上下文 中的变量和函数的关系(负责处理 letconst 定义的变量)
      1. 创建 外部环境引用,值为 null
    1. 创建全局上下文的 变量环境

      1. 创建 对象环境记录器,它持有 变量声明语句 在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
      1. 创建 外部环境引用,值为 null
    1. 确定 this 值为全局对象(以浏览器为例,就是 window

函数被调用,函数上下文被创建

    1. 创建函数上下文的 词法环境

      1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 letconst 定义的变量)
      1. 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
    1. 创建函数上下文的 变量环境

      1. 创建 声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)
      1. 创建 外部环境引用,值为全局对象,或者为父级词法环境(作用域)
    1. 确定 this

在上下文创建阶段,引擎会检查代码找出变量和函数声明,并进行相应的初始化设置。对于 var 变量,初始值为 undefined;对于 let 和 const 变量,在声明前访问会得到引用错误。

总的来说,ES5 执行上下文的变更主要是概念上的调整,本质与 ES3 差别不大,更多是为 ES6 中的新特性做铺垫。

对比ES3和ES5、ES6在执行上下文方面存在的差异:

变量对象和活动对象

  • ES3 中有变量对象(VO)和活动对象(AO)的概念,函数执行时变量对象会激活为活动对象。
  • ES5 去除了变量对象和活动对象,以词法环境组件(LexicalEnvironment component)和变量环境组件(VariableEnvironment component)替代。

词法环境和变量环境

  • ES5 中的词法环境是一种规范类型,基于代码的词法嵌套结构定义标识符和变量、函数的关联。
  • ES5 中的变量环境也是词法环境,为 ES6 服务,在 ES6 中词法环境组件和变量环境在存储变量绑定上有不同。
  • ES6引入了块级作用域,每个块级作用域都会创建新的词法环境 ES5只有全局作用域和函数作用域

变量初始化

  • ES3 中函数执行上下文阶段,变量统一定义为 undefined,函数直接定义。
  • ES5 中在上下文创建阶段,引擎检查代码找出变量和函数声明,变量最初会设置为 undefinedvar 情况下),或者未初始化(letconst 情况下)。

this绑定

  • ES5中普通函数的this由调用方式决定
  • ES6的箭头函数没有自己的this,继承自外层作用域的this

执行上下文的创建过程

  • ES3 中函数执行上下文创建阶段主要包括初始化变量对象、构建作用域链、确定 this 值。
  • ES5 中全局上下文和函数上下文创建过程更加复杂和细致,包括创建词法环境变量环境的对象环境记录器、外部环境引用等,并确定 this 值。

变量对象的创建过程

(我理解环境记录器应该是相同逻辑):

  1. 函数的所有形参 (如果是函数上下文)

    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  2. 函数声明

    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 变量声明

    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

参考链接: