JS执行上下文

174 阅读5分钟

说明

  • 本文是针对ES6标准来说的(但好像是从ES5就已经这样了),和ES3的标准有区别
  • 本来并非原创文章,知识点基本来自参考文章和ES6标准文档的翻译(google)
  • 文章中可能存在错误

1 执行上下文是什么

执行上下文(Execution Context,也有翻译是执行环境)是JS中最为重要的一个概念,它定义了变量或者函数有权访问的其他数据,决定了他们各自的行为。 执行环境有三种类型

  • 全局执行上下文
    是最外围的一个执行上下文,根据执行ESMAScript实现所在的宿主环境不同,表示执行上下文的对象也不一样。在web浏览器中,全局执行上下文被认为是window对象。
    某个执行上下文中的所有代码执行完毕后,该环境被销毁,保留在其中的所有变量和函数定义也随之销毁。全局执行上下文直到应用程序退出,例如关闭网页或浏览器时才会被销毁。
  • 函数执行上下文
    每个函数都有自己的执行上下文
  • Eval执行上下文
    eval代码特定的环境,本文后续都不涉及。

2 如何创建执行上下文

创建执行环境有两个阶段

2.1 创建阶段

创建阶段会发生三件事:this绑定、创建词法环境组件、创建环境变量组件

2.1.1 this绑定

在全局执行上下文中,this指向全局对象。
在函数执行上下文中,this的值取决于函数调用。如果函数被一个引用对象调用,则this指向那个对象。否则this的值被设置为全局对象或者undefined(严格模式)。

2.1.2 词法环境

描述

词法环境(Lexical Environments)是一种规范类型,用于根据ECMAcript代码的词法嵌套结构定义标识符与特定变量和函数的关联。

一个词法环境由一个环境记录(EnvironmentRecord)和一个对外部词法环境(可能是空)的引用组成。

通常,词法环境与ECMAScript代码的某些特定语法结构相关联,例如FunctionDeclaration、BlockStatement或TryStatement的Catch子句,并且每次评估此类代码都会创建一个新的词法环境

说明

  • 这里的标识符指的是变量或函数的名字
  • 外部环境的引用指的是它可以访问的父级词法环境
  • ECMAScript的类型有两种(8 类型
    • 语言类型
      指的是可以在JS中使用的,比如Null,Number,String、object等

    • 规范类型
      描述ECMAScript运算的中途结果,这些只不能操作,只存在于规范中,包括Reference、List、Propoerty Identifier、Lexical Environment、Enviroment Record等

类型

  • 全局环境
    没有外部环境引用,环境记录器是对象环境记录器
  • 函数环境
    环境记录器是声明式环境记录器

环境记录器

Environment Records,是一种规范类型,用于定义标识符与特定变量和函数的关联。
通常,环境记录与代码的某些特定语法结构想关联,例如FunctionDeclaration、BlockStatement 或 TryStatement 的 Catch 子句。

和上面词法环境的描述差不多,因为就是它的主要组成部分

环境记录有三种类型

  • 声明式环境记录器(Declarative Environment Record)
    绑定范围内的标识符级,包含let、const、class、module、function等。包含两个子类:
    • 函数环境记录器(Function Environment Records )
    • 模块环境记录器(Module Environment Records)
  • 对象环境记录器(Object Environment Record)
    定义出现在全局执行环境中的变量和函数的关系
  • 全局环境记录器(Global Environment Record)
    用于脚本全局声明,[[outerEnv]]为空(这个不知道干啥用的)。可以预先填充标识符绑定,并包括一个关联的全局对象。

2.1.3 变量环境

它同样是一个词法环境,其环境记录器记录变量声明语句在执行上下文中创建的绑定关系。
在ES6中,词法环境组件和变量环境组件的区别是,前者用来存储函数声明和let、const变量绑定,后者用来存储var变量绑定。

2.1.4 Demo

const assign = require('lodash/assign');
const user = {
    nick: '李向维',
    sex: '女',
};

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

function add(x, y) {
    var result = x + y;
    return result;
}

c = add(a, b);

console.log(assign(user));

全局执行上下文

GlobalExectionContext = {
    ThisBinding: <Global Object>,

    LexicalEnvironment: {
        EnviromentRecord: {
            Type: "Object",
            // 绑定标识符
            a: <uninitialized>,
            b: <uninitialized>,
            user: <uninitialized>,
            lodash: { // Module Environment Records
                Type: "Module",
                assign: { // Function Environment Records
                    Type: "Function",
                    Arguments: null,//vs debug的时候是这样显示的
                    length: 0,//vs debug的时候是这样显示的
                }
                ...
            },
            add: { // Function Environment Records
                Type: "Function",
                Arguments: {},// 自己写的 可能有误
                length: 2
            },
        },
        outer: <null>
    },

    VariableEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            c: undefined,
        },
        outer: <null>
    }
}

当调用到add函数时,会创建函数执行上下文

FunctionExecutionContext = {
    ThisBinding: <Global Object>,

    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            Arguments: {0: 20, 1: 10, length: 2},
            x: 20,
            y: 10,
        },
        outer: <GlobalLexicalEnvironment>
    },

    VariableEnvironment: {
        EnvironmentRecord: {
            Type: "Delcarative",
            result: undefined,
        },
        outer: <GlobalLexicalEnvironment>
    }
}

注意到这里,var声明的和let、const声明的变量的设置值不一样。反映到代码运行中,就是var定义的变量可以在声明之前访问,也就是变量提升,但let和const声明的不行,会报错。

2.2 执行阶段

在此阶段,完成对变量的分配,然后执行代码。
注意,此时如果JS引擎不能在源码中声明的实际位置找到let变量的值,它会被赋值为undefined。

3 如何管理执行上下文

全局上下文只有一个,但在代码执行中可能会有很多的函数上下文,这些上下文又是如何被管理的?

执行上下文栈(Execation Context Stack),是用来管理执行上下文的数据结构,存储了代码执行期间所有的执行上下文。
它是有大小的,入栈的上下文太多,可能会发生栈溢出。这在递归中容易出现。

Xnip2022-09-14_09-20-34.jpg

用开发工具的debug可以观察到栈的进入和移出,this指向等。 Xnip2022-09-14_09-53-25.jpg

参考文档