持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
顺序执行?
在刚刚学习 JavaScript 的时候, 我们都会被告知, JS 的代码是顺序执行的, 自上到下, 逐行执行. 但是当我们深入一些学习之后, 或者说我们去刷过 JS 的执行顺序面试题,就会知道, JS好像也并不是逐行分析逐行执行啊, 就比如下面这个例子
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
为啥不是 foo1 和 foo2 呢?
其实 JS 引擎并不是一行一行的解释执行代码, 而是一段段的执行, 当执行一段代码的时候. 会进行一些 "准备工作". 例如: 上面例子中的函数提升
可执行代码
在 JS 中, 可执行代码(executable code)分为
- 全局代码
- 函数代码
- eval 代码
在一段可执行代码要被执行的时候,就会进行一些准备工作, 用专业的说法就是, 创建 执行上下文(execution context).
执行上下文栈
全局代码就一个份, eval 使用非常少, 都还好说, 在任何一个程序中, 函数可是多的数都数不清, 每一个函数执行都需要创建 执行上下文, 那么如何管理这些上下文信息呢?
所以 JS 引擎创建了 执行上下文栈(Execution context stack, ECS) 用来管理上下文
栈: 一种先进后出后进先出的数据结构
接下来让我们来模拟一下执行上下文栈的行为, 来帮助理解
这里我们采用数组来模拟
const ECStack = [];
在 JS 要执行代码的时候, 最先执行的肯定是全局代码, 所以初始化的时候, 首先会向栈里放入 全局执行上下文, 这里我们采用 globalContext 来表示, 我们要知道,全局执行上下文会一直在栈底, 直到应用程序结束的时候, 才会被清空
const ECStack = [
globalContext
]
接下来我们遇到以下 可执行代码
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
当执行一个函数的时候,就会创建一个执行上下文, 并压入执行上下文栈中, 当函数执行完毕之后, 就会将函数执行上下文从栈中弹出. 知道了这样的工作原理,让我们来看看如何处理上面这段代码:
// 执行 fun1
ECStack.push(<fun1> functionContext);
// 执行 fun2
ECStack.push(<fun2> functionContext);
// 执行 fun3
ECStack.push(<fun3> functionContext);
// 执行完毕
ECStack.pop(); // fun3执行完毕 出栈
ECStack.pop(); // fun2执行完毕 出栈
ECStack.pop(); // fun1执行完毕 出栈
// 然后去执行别的代码, 全局上下文除了应用结束,否则是不会出栈的
我们来看下动画的执行