读懂JS核心(一)--执行栈和执行上下文

229 阅读4分钟

要想学会JS,正确理解执行上下文和执行栈是至关重要的。只有理解了执行上下文和执行栈,才能更好地理解其他 JavaScript 概念(如变量声明提升,作用域和闭包),让我们开始学习吧!

执行栈和执行上下文

执行栈

执行栈,也叫调用栈,用于存储在代码执行期间创建的所有执行上下文。

首次运行JS代码时,会创建一个全局执行上下文,并push到当前执行栈中。每当发生函数调用,JS引擎都会为该函数创建一个新的函数执行上下文,并push到当前执行栈的栈顶。

根据执行栈LIFO的规则,当栈顶函数运行完成后,其对应的函数执行上下文会从执行栈中pop出,上下文控制权将移到当前执行栈的下一个执行上下文。

当所有代码执行完毕,JS引擎从当前栈中移除全局执行上下文。

执行上下文

执行上下文是当前JS代码被解析和执行所在环境的抽象概念。

执行上下文分为三种类型:

  • 全局执行上下文:在浏览器中一般指的是window对象。
  • 函数执行上下文:每次调用函数都会创建一个新的函数执行上下文。
  • Eval函数执行上下文:~~~~运行在 eval 函数中的代码,少用并且不建议使用

执行上下文的创建

执行上下文的创建分为两个阶段:创建阶段、执行阶段。

创建阶段

  1. 确定this的值(thisBinding)

    在全局执行上下文中,this的值指向全局对象。

    在函数执行上下文中,this的值取决于函数的调用方式(默认绑定、隐式绑定、显式绑定、new绑定、箭头函数)。

  2. 创建词法环境(lexicalEnvironment)

    词法环境是一种规范类型,用于定义标识符和具体变量和函数的关联.

    词法环境由两个组件组成:

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

    环境记录器有两种类型:

    声明式环境记录器(Declarative):存储变量、函数、参数

    对于函数环境,Declarative类型的环境记录器还包含一个传递给函数的arguments对象和传递给函数的参数的length。

    对象环境记录器(Object):定义出现在全局上下文中的变量和函数的关系。

    总结:

    在全局环境中,环境记录器是Object类型的;在函数环境中,环境记录器是Declarative类型的。

    (2). 一个外部环境的引用:意味着它可以访问其父级词法环境(及作用域)

    词法环境有两种类型:

    (1). 全局环境

    (1). 函数环境

  3. 创建变量环境(variableEnvironment)

    变量环境也是一个词法环境,因此它具有词法环境的所有属性。

    ES6规定:词法环境用于const和let绑定,变量环境用于var绑定。


通过一个例子🌰来了解执行上下文的创建:

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

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

c= multiply(a, b);

执行上下文如下:

// 全局执行上下文
GlobalExectionContext = {
    thisBinding: <Global Object>,
    lexicalEnvironment: {
        environmentRecord: {
            type: "Object",
            a: <uninitialized>,
            b: <uninitialized>,
            multiply: <func>,
        },
        outer: <null>
    },
    varibleEnvironment: {
        environmentRecord: {
            type: "Object",
            c: undefined,
        },
        outer: <null>
    },
}

// 函数执行上下文
FunctionExectionContext = {
    thisBinding: <Global Object>,
    lexicalEnvironment: {
        environmentRecord: {
            type: "Declarative",
            Arguments: {
                0: 20,
                1: 30,
                length: 2,
            },
        },
        outer: <GlobalExectionContext>
    },
    varibleEnvironment: {
        environmentRecord: {
            type: "Declarative",
            g: undefined
        },
        outer: <GlobalExectionContext>
    }

执行阶段

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

在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined

补充:为什么会出现变量提升?

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

总结

在这一节,我们详细介绍了JS中的执行栈和执行上下文,还解释了出现变量提升的原因。接下来我们会继续介绍其他JS核心知识。

我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

如有问题,欢迎在留言区一起讨论。