JavaScript 调用栈和执行上下文

执行上下文和执行栈

执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。变量或函数的执行上下文决定了它们能够访问哪些数据以及它们的行为。

执行上下文的类型

  • 全局执行上下文

    默认或者说基础的上下文,所有不在函数内部的代码都在全局上下文中执行。它会创建一个全局对象 window 并将 this 指向它(浏览器环境)。所有通过 var 定义的全局变量和方法都会成为 window 对象的属性和方法。使用 letconst 定义的顶级声明不会定义在全局上下文中,但在作用域链的解析效果上是一样的。

    一个程序中只会有一个全局执行上下文。

  • 函数执行上下文

    每当一个函数被调用时,都会为该函数创建一个新的上下文。

    每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。

  • eval 函数上下文

    执行在 eval 函数内部的代码也会有它属于自己的执行上下文。

执行上下文的内容

ES 5 中执行上下文包含了:

  • 词法环境
  • 变量环境
  • this

词法环境

JavaScript 中每个运行的函数,代码块以及整个脚本,都有一个被称为词法环境的隐藏的内部关联对象。而词法环境对象又由两部分组成:

  • 环境记录 - 存储所有局部变量
  • 一个对外部词法环境的引用

当代码要访问一个变量时,首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。

执行栈(调用栈)

执行栈(或者调用栈),也就是在其它编程语言中所说的调用栈,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

执行上下文的创建

创建执行上下文有两个阶段:创建阶段和执行阶段

创建阶段

JavaScript 代码执行前,执行上下文将经历创建阶段

  1. this 绑定

    在全局执行上下文中,this 值是全局对象。

    在函数执行上下文中,this 的值取决于函数是如何被调用的。如果被一个引用对象调用,this 的值会被设置为这个对象,否则会被设置为 undefined 或全局对象。

  2. 词法环境组件的创建

    词法环境是一种持有 标识符 - 变量 映射的结构。词法环境的内部有两个组件 :环境记录器和一个外部环境的引用。环境记录器是存储变量和函数声明的实际位置,外部环境引用意味着你可以用它访问其父级词法环境。

    • 全局词法环境

      全局词法环境是没有外部环境引用的词法环境(在全局执行上下文中)。

    • 函数词法环境

      函数内部用户定义的变量存储在环境记录器中。它的外部环境引用可能是全局环境或者任何包含此函数的外部函数。

    环境记录器的两种类型 :

    • 声明式环境记录器存储变量、函数和参数
    • 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系

    简而言之,在全局环境中,环境记录器就是对象环境记录器,在函数环境中,环境记录器就是声明式环境记录器。

  3. 变量环境组件的创建

    变量环境实际上也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。

    ES 6 中,词法环境和变量环境的一个不同就是前者被用来存储函数声明和变量绑定(letconst),后者只用来存储 var 变量绑定。

执行阶段

在执行阶段,完成对所有变量的分配,最后执行代码。

执行上下文的销毁

执行上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。

闭包

闭包是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在外部函数被返回之后;也可以理解为闭包就是一个绑定了执行环境的函数。

JavaScript 中,函数是天生闭包的。

作者

Y2hlbmdsZWk=

发布于

2019-04-22

更新于

2021-09-01

许可协议