执行上下文的创建、执行与管理

625 阅读6分钟

什么是执行上下文?

简而言之,执行上下文就是JavaScript代码执行的环境的抽象概念。每当JavaScript代码在运行的时候,他都是在执行上下文中运行。

执行上下文的类型

  • 全局执行上下文,这是默认或者基础的环境,任何不在函数中执行的代码,都会在基础环境中执行。在基础环境中执行会有两步:创建一个全局的window对象(浏览器环境中),并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文,每当一个函数被调用的时候,都会为该函数创建一个执行的环境,也就是执行上下文,每个函数都有自己的执行上下文,且都是在被调用的时候创建的。函数上下文可以有多个,创建的时候压入栈,执行完出栈。
  • Eval函数执行上下文,执行在eval函数内部的代码也有自己的执行上下文,但由于JavaScript开发不经常使用eval,所以不谈论,单需要有人家奥~

理解上,我把全局执行上下文比作这个世界,世界上有好多小房子,每个小房子就是函数执行上下文,我们不管做什么事都会在世界里这个基本的环境里,当进入每一个小房子就相当于有了一个新的环境,这个时候要是做一些操作,就相当于在小房子这个新环境里了。可能还有些偏差。想想还有什么更通俗易懂的解释方法,毕竟看了好久官方解释才看懂哇~

执行栈

执行栈,就是其他编程语言中“调用栈”,说到底还是数据结构中的“先进后出”的栈,对应的是队列,是用来存储代码执行时的所创建的所有执行上下文,也就是所有环境。

JavaScript引擎第一次遇到脚本时,会先创建一个全局的执行上下文环境,并且压入当前的执行栈。每当引擎遇到一个函数时,都会创建一个函数的执行上下文,也是压入当前的执行栈的顶部,当函数执行完毕时,该执行上下文出栈。

JavaScript执行代码并不是说一行一行的执行,更准确点说应该是一段一段的顺序执行。

上面所说的都是JavaScript如何管理执行栈,那JavaScript引擎是如何创建一个执行上下文的?

如何创建一个执行上下文

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

创建阶段

创建阶段一共经历3步操作:

  • 确定this值的绑定
  • 创建 词法环境(LexicalEnvironment) 组件
  • 创建 变量环境(VariableEnvironment) 组件

下面我们详细的看一下每一步都具体做什么操作:

this值绑定

  • 全局执行上下文中,this指向全局对象,浏览器中就是window对象,而在nodejs中就是这个文件的module对象
  • 函数执行上下文中,当然就是指向最后调用函数那个对象

词法环境(LexicalEnvironment)

词法环境包含两部分:

  • 环境记录器:存储变量和函数声明的具体位置
  • 对外部环境的引用:可以访问的外部的词法环境

词法环境包含两种类型:

  • 全局词法环境,全局环境的外部环境的引用是null,拥有内建的原生对象,比如:Object、Array等,环境记录器中记录着全局对象还有任何用户定义的一些变量,this值指向全局对象。
  • 函数词法环境,环境记录器存储着函数内部的一些变量,外部环境引用可能是全局对象,也可能是包含该函数的外部词法环境。

环境记录器也包含两种类型:

  • 对象环境记录器(全局环境中)
  • 声明式环境记录器(函数环境中)

在函数环境,声明式环境记录器还包含一个传递给函数的arguments对象(此对象存储索引和参数的映射)和传递给函数的参数的length

下面用一张图整理表示就是:


用伪代码表示词法环境的话:

//全局执行上下文
GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: 'Object',
            //标识符
        },
        outer: <null>
    }
}

//函数执行上下文
FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: 'Declarative',
            Arguments: { length }
            //标识符
        },
        outer: <Global or outer function environment reference>
    }
}

变量环境(VariableEnvironment)

变量环境说到底也是一个词法环境,环境记录器同样记录着执行上下文创建的时候变量声明的绑定关系。

词法环境与变量环境唯一的区别就是:

  • 词法环境的环境记录器存储的是存储函数声明和变量(let const类型)
  • 变量环境存储的是变量的绑定(var类型)

代码理解

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20, 30);

<!--全局执行上下文-->
    <!--词法环境-->
        <!--环境记录器-->
            <!--Type-->
            <!--a-->
            <!--b-->
            <!--multiply-->
        <!--外部环境引用-->
    <!--变量环境-->
         <!--环境记录器-->
            <!--Type-->
            <!--c-->
        <!--外部环境引用-->

<!--函数执行上下文-->
    <!--词法环境-->
        <!--环境记录器-->
            <!--Type-->
            <!--Arguments-->
        <!--外部环境引用-->
    <!--变量环境-->
        <!--环境记录器-->
            <!--Type-->
            <!--g-->
        <!--外部环境引用-->

//全局执行上下文
GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: 'Object',
            a: < uninitialized >,
            b: < uninitialized >,
            multiply: < func >
        },
        outer: <null>
    },
    VariableEnvironment: {
        EnvironmentRecord: {
            Type: 'Object',
            c: undefined
        },
        outer: <null>
    }
}

//函数执行上下文
FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: 'Declarative',
            Arguments: { 0: 20, 1: 30, length: 2 }
        },
        outer: < GlobalLexicalEnvironment >
    },
    VariableEnvironment: {
        EnvironmentRecord: {
            Type: 'Declarative',
            g: undefined
        },
        outer: < GlobalLexicalEnvironment >
    }
}

只有遇到multiply函数被调用,函数执行上下文才会被创建

那为什么letconst类型并没有被初始化,而var类型的变量被赋undefined

这是因为在变量词法环境中,标识符的对应关系是undefined而不是未初始化

这就是为什么你可以在声明之前访问 var 定义的变量(虽然是 undefined),但是在声明之前访问 let const 的变量会得到一个引用错误。

也就是所谓的变量提升

执行阶段

执行阶段应该比较好理解。

JavaScript引擎遇到一段脚本,首先会看一遍这段代码有什么变量,这些变量无论是基础类型的还是引用类型或者函数类型,也就是上面的创建阶段。

后面就是从上往下依次执行代码,包括变量赋值或者函数执行,创建函数执行上下文。

  • 全局上下文的变量对象初始化是全局对象
  • 函数上下文的变量对象初始化只包括 Arguments 对象
  • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  • 在代码执行阶段,会再次修改变量对象的属性值

结语

这只是执行上下文相关内容的一个开篇,抽时间来不断的充实自己是一个非常重要的过程,不要被业务覆盖了我们美好的生活,加油吧,少(sao)年----❤