前言:
深入学习js的执行栈有助于我们更全面的了解js的运行机制
一 执行栈的介绍
-
调用栈(就是执行栈):在 JavaScript 中,每当一个函数被调用时,这个函数的上下文(在这里把上下文当成一个对象就可以)就会被推入调用栈的顶部。当函数执行完成后,其上下文会从栈顶弹出,然后控制权会返回到之前调用该函数的地方。
-
栈帧(Stack Frame):调用栈中的每一个条目被称为栈帧。每个栈帧都包含了函数的局部变量、参数、返回值以及函数的执行状态。就理解为每个函数被调用就有一个栈帧。调用它的函数会有一个调用帧。栈帧的作用是提供一个储存空间。函数执行完就被释放掉。
-
栈顶:它是调用栈中最后被推入的元素,表示当前正在执行的函数
二 执行过程
首先我们要知道栈是后进先出(LIFO)的结构。要执行最下面的函数就要把所有函数执行完,并且每次执行都只能先执行栈顶函数,同时,栈的入栈和出栈都发生在栈顶。
就如图中,一个函数压着一个函数,必须先执行最上面的函数,最后在执行栈底的函数。如果函数5执行完。又入栈了一个函数6,那么就要先执行函数6。
可以用代码来模拟执行机制:
// 模拟执行栈
class ExecutionStack {
constructor() {
this.stack = [];//stack当成是执行栈
}
// 将函数推入栈
push(func) {
this.stack.push(func);
}
// 从栈中弹出函数并执行
pop() {
if (this.stack.length === 0) return;
const func = this.stack.pop();
func();
}
// 检查栈是否为空
isEmpty() {
return this.stack.length === 0;
}
}
// 定义一些模拟的函数
function func1() {
console.log('1');
}
function func2() {
// 在 func2 中调用 func3
this.func3()
console.log('2');
}
function func3() {
console.log('3');
}
// 创建执行栈实例
const executionStack = new ExecutionStack();
// 将函数推入栈并执行
executionStack.push(func1);
executionStack.push(func2);
// 模拟执行过程
while (!executionStack.isEmpty()) {
executionStack.pop();
}
//输出3,2,1
分析:
准备过程:创建一个栈实例,push函数是入栈,pop是出栈操作。while开始才是模拟执行过程。pop会将函数出栈并执行。
1.executionStack.push(func1)将func1推入栈,executionStack.push(func2)将func2推入栈,其实都是是推入stack这个数组也是模拟栈中。
2.执行栈顶func2,创建栈帧。func2第一行调用了func3,这里等于将func3进行了入栈。func3入栈立马创立栈帧,开始执行func3函数,打印3后执行完成就会出栈。接着执行func2打印2。func2执行完后就出栈。
3.此时,执行栈就剩func1。执行func1函数,打印出1。