52. 什么是执行栈(Call Stack)?如何造成栈溢出? 副本.md

查看飞书原文 ↗3,259 字符

1. 核心概念 (Core Concept)

执行栈(Call Stack) 是一种用于管理 JavaScript 代码执行上下文的数据结构,它本质上是一个后进先出(LIFO)的栈。每当一个函数被调用时,一个新的“栈帧”(Stack Frame)就会被推入执行栈的顶部;当函数执行完毕并返回时,其对应的栈帧就会从栈顶弹出。这个过程决定了函数调用的顺序。

2. 为什么需要它? (The "Why")

执行栈是 JavaScript 引擎执行代码时不可或缺的一部分,它主要解决了以下问题:

3. API 与用法 (API & Usage)

执行栈是 JavaScript 引擎的内部机制,JavaScript 代码本身并没有直接操作执行栈的 API。我们主要通过函数调用来隐式地影响执行栈的行为。

当执行一段 JavaScript 代码时:

  1. 全局代码首先创建一个全局执行上下文,并将其压入执行栈底部。
  2. 当调用一个函数时,引擎为该函数创建一个新的执行上下文,并将其压入栈顶。
  3. 当函数执行完毕并 return 或遇到代码末尾时,该函数的执行上下文从栈顶弹出。
  4. 如果函数抛出未捕获的错误,执行栈会“展开”(unwind),弹出栈帧直到找到合适的错误处理机制(如 try...catch),或者直到栈底,此时程序终止。

经典的函数调用示例:

function firstFunction() {
  console.log("Entering firstFunction");
  secondFunction();
  console.log("Exiting firstFunction");
}

function secondFunction() {
  console.log("Entering secondFunction");
  // Some code
  console.log("Exiting secondFunction");
}

// Call Stack: [] -> [Global]
firstFunction();
// Call Stack (while firstFunction is running): [Global] -> [Global, firstFunction]
// Call Stack (while secondFunction is running): [Global, firstFunction] -> [Global, firstFunction, secondFunction]
// Call Stack (after secondFunction finishes): [Global, firstFunction]
// Call Stack (after firstFunction finishes): [Global]
// Call Stack (after script finishes): []

在这个例子中,执行栈的变化如下:

  1. 程序开始,全局上下文压栈:[Global]
  2. 调用 firstFunction,其上下文压栈:[Global, firstFunction]
  3. 在 firstFunction 中调用 secondFunction,其上下文压栈:[Global, firstFunction, secondFunction]
  4. secondFunction 执行完毕,其上下文弹出:[Global, firstFunction]
  5. firstFunction 执行完毕,其上下文弹出:[Global]
  6. 全局代码执行完毕,全局上下文弹出:[]

栈溢出(Stack Overflow)

栈溢出发生在执行栈中推入了过多的栈帧,超出了引擎分配的栈空间。这通常是由于递归函数没有正确定义终止条件,导致函数无限或极大地递归调用自身。每次递归调用都会创建一个新的栈帧,最终耗尽内存。

如何造成栈溢出(典型示例 - 无休止的递归):

function CauseStackOverflow() {
  // 没有终止条件
  CauseStackOverflow();
}

// 调用这个函数会导致栈溢出错误
// CauseStackOverflow();

运行上述代码会看到类似 RangeError: Maximum call stack size exceeded 的错误。

4. 关键注意事项 (Key Considerations)

5. 参考资料 (References)


原文链接与附件索引