一次弄懂JavaScript执行上下文

566 阅读6分钟

一、执行上下文的概念

JS引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作”,就叫做 "执行上下文(execution context 简称 EC)"

二、执行上下文的类型

全局执行上下文

只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。

函数执行上下文

存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。

eval函数执行上下文

指的是运行在 eval 函数中的代码,很少用而且不建议使用。

三、执行上下文栈

作用

用于管理执行在代码执行期间创建的执行上下文

特点

1.单线程,在主进程上运行
2.同步执行,从上往下按顺序执行
3.全局上下文只有一个,浏览器关闭时会被弹出栈
4.函数的执行上下文没有数目限制
5.函数每被调用一次,都会产生一个新的执行上下文环境

每当有函数被调用时,引擎都会为该函数创建一个新的函数执行上下文然后推入栈中。 当栈顶的函数行完毕后,所对应的上下文出栈,继续执行栈顶的函数

四、执行上下文的生明周期

创建阶段

1、This Binding

1)全局执行上下文中 this 的值指向全局对象,在浏览器中this 的值指向 window 对象,而在nodejs中指向这个文件的module对象。

2)函数执行上下文中this 的值取决于函数的调用方式。 具体有:默认绑定、隐式绑定、显式绑定、new绑定、箭头函数。

2、创建词法环境(LexicalEnvironment)

词法环境有两个组成部分

1)环境记录:存储变量和函数声明的实际位置

2)对外部环境的引用:可以访问其外部词法环境

词法环境有两种类型

1)全局环境 :是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。

2)函数环境 :用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。

创建变量环境(VariableEnvironment)

1)变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。

2)在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量 let 和 const 绑定,而后者仅用于存储变量var绑定。

执行阶段

执行阶段主要做三件事情

1、变量赋值

2、函数引用

3、执行其他的代码

注意 :如果 Javascript 引擎在源代码中声明的实际位置找不到 var 变量的值,那么将为其分配 undefined 值。

销毁阶段

执行完毕出栈,等待回收被销毁

五、实例讲解

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

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

c = multiply(20, 30);

// ----------------------------------------
GlobalExectionContext = {
    // this绑定
    ThisBinding: < Global Object > ,
    // 创建词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Object",
            // 标识符绑定在这里  
            a: < uninitialized > ,
            b: < uninitialized > ,
            multiply: < func >
        }
        outer: < null >
    },
    // 创建变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
            Type: "Object",
            // 标识符绑定在这里  
            c: undefined,
        }
        outer: < null >
    }
}
// 函数执行上下文
FunctionExectionContext = {
    // this绑定
    ThisBinding: < Global Object > ,
    // 创建词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            // 标识符绑定在这里  
            Arguments: {
                0: 20,
                1: 30,
                length: 2
            },
        },
        outer: < GlobalLexicalEnvironment >
    },
    // 创建变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            // 标识符绑定在这里  
            g: undefined
        },
        outer: < GlobalLexicalEnvironment >
    }
}

变量提升的原因: 在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。

六、执行上下文总结

对于 ES5 中的执行上下文,我们可以用下面这个列表来概括程序执行的整个过程:

1.程序启动,全局上下文被创建

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

    1.1.1创建对象环境记录器它用来定义出现在全局上下文中的变量和函数的关系(负责处理 let 和 const 定义的变量)

    1.1.2创建外部环境引用,值为 null

  1.2创建全局上下文的变量环境

    1.2.1创建对象环境记录器,它持有变量声明语句在执行上下文中创建的绑定关系(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)

    1.2.2创建外部环境引用,值为 null

    1.2.3确定 this 值为全局对象(以浏览器为例,就是 window )

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

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

    2.1.1创建声明式环境记录器,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 let 和 const 定义的变量)

    2.1.2创建外部环境引用,值为全局对象,或者为父级词法环境(作用域)

  2.2创建函数上下文的变量环境

    2.2.1创建声明式环境记录器 ,存储变量、函数和参数,它包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。(负责处理 var 定义的变量,初始值为 undefined 造成声明提升)

    2.2.2创建外部环境引用,值为全局对象,或者为父级词法环境(作用域)

  2.3确定 this 值

3.进入函数执行上下文的执行阶段:

  3.1在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。

参考

JavaScript进阶-执行上下文(理解执行上下文一篇就够了) JavaScript执行上下文-执行栈