Skip to content

执行上下文

执行上下文的概念

执行上下文,也可以称为执行环境,其实就是 JavaScript 代码在运行前,创建的一块内存空间,其中包含了代码运行所需要的内容。这个过程,我们称为预编译。

JavaScript 执行上下文分为三种:

  • 全局执行上下文,它是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于函数之外的所有代码而创建的
  • 函数执行上下文,每个函数在执行的时候都会创建自己的执行上下文
  • eval 函数执行上下文(不推荐使用),使用 eval 函数也会创建一个新的执行上下文

创建执行上下文

JavaScript 代码运行时,首先会进入全局环境,对应的会创建全局执行上下文。而代码中基本都会存在函数,只要调用一次函数,就会创建一个该函数的执行上下文。

创建执行上下文的步骤如下:

  • 确定 this 指向

    • 在全局执行上下文中,this 指向 window 对象
    • 在函数执行上下文中,this 指向 window 对象
    • 在对象中调用函数,this指向该对象
    • 使用new关键字调用函数,this指向对象实例
  • 创建变量对象(Variable Object)

    • 确定函数的形参值和特殊变量arguments
    • 确定普通字面量形式的函数声明
    • 确定变量声明、函数表达式声明

执行栈

在 JavaScript 中,通过栈的存取方式来管理执行上下文,我们称之为执行栈(Call Stack)。

栈遵循先进后出,后进先出的规则,也称为 LIFO(Last In First Out)规则。

接下来我们来分析 JavaScript 中如何通过栈来管理通过执行上下文:

  • 代码执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈)

  • 代码执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文

因为 JavaScript 在执行代码时最先进入全局环境,所以处于栈底的永远是全局环境的执行上下文。而处于栈顶的是当前正在执行函数的执行上下文

当函数调用完成后,它就会从栈顶被推出,理想的情况下,闭包会阻止该操作。

而全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底。

下面我们来看一段具体的代码示例:

js
function foo () { 
    function bar () {        
      return 'I am bar';
    }
    return bar();
}
foo();

对应图解如下:

image-20220819172344575

执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。

js
// 递归调用自身
function foo() {
    foo();
}
foo();
// 报错: Uncaught RangeError: Maximum call stack size exceeded

总结

执行上下文,其实就是 JavaScript 代码在运行前,创建的一块内存空间,其中包含了代码运行所需要的内容。

JavaScript 执行上下文分为三种:

  • 全局执行上下文,它是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于函数之外的所有代码而创建的
  • 函数执行上下文,每个函数在执行的时候都会创建自己的执行上下文
  • eval 函数执行上下文(不推荐使用),使用 eval 函数也会创建一个新的执行上下文

JavaScript 代码运行时,进入全局环境或调用函数时,都会创建对应的执行上下文。

创建执行上下文的步骤如下:

  • 确定 this 指向

    • 在全局执行上下文中,this 指向 window 对象
    • 在函数执行上下文中,this 指向 window 对象
    • 在对象中调用函数,this 指向该对象
    • 使用 new 关键字调用函数,this 指向对象实例
  • 创建变量对象(Variable Object)

    • 确定函数的形参值和特殊变量arguments
    • 确定普通字面量形式的函数声明
    • 确定变量声明、函数表达式声明

JavaScript 通过执行栈来管理执行上下文,栈的特点是先进后出,后进先出

这代表着全局执行上下文始终在栈底,当前正在执行函数的函数执行上下文始终在栈顶,函数执行完毕后,就会从栈顶被推出。