当我们调用一个函数时(激活),一个新的执行上下文就会被创建。
一个执行上下文的生命周期可分为 创建阶段 和 代码执行阶段 两个阶段。
创建阶段:在这个阶段中,执行上下文会分别进行以下操作
代码执行阶段:创建完成之后,就会开始执行代码,并依次完成以下步骤
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。
执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
JavaScript 中的运行环境大概包括三种情况:
因此在一个 JavaScript 程序中,必定会产生多个执行上下文,而 JavaScript 引擎会以栈的方式来处理它们,这个栈,我们称其为 函数调用栈(Call Stack)。栈底永远都是全局上下文,而栈顶就是当前执行的上下文。
当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。
JavaScript 引擎通过创建 执行上下文栈(Execution Context Stack,ECS) 用于管理执行上下文。
🎯 为了模拟执行上下文栈的行为,让我们类比执行上下文栈是一个数组。
ECStack = [];
试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext
表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext
。
ECStack = [globalContext];
现在 JavaScript 遇到下面的这段代码了:
function fun3() {console.log('fun3');}function fun2() {fun3();}function fun1() {fun2();}fun1();
当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
知道了这样的工作原理,让我们来看看如何处理上面这段代码:
// fun1()ECStack.push(<fun1> functionContext);// fun1 中竟然调用了 fun2,还要创建 fun2 的执行上下文ECStack.push(<fun2> functionContext);// 擦,fun2 还调用了 fun3!ECStack.push(<fun3> functionContext);// fun3 执行完毕ECStack.pop();// fun2 执行完毕ECStack.pop();// fun1 执行完毕ECStack.pop();// JavaScript 接着执行下面的代码,但是 ECStack 底层永远有个 globalContext
详细了解了这个过程之后,我们就可以对 执行上下文栈 总结一些结论了。