(
调用堆栈
调用堆栈是 JavaScript 中的一种数据结构,其中包含正在执行的函数。此结构采用后进先出格式。让我们看一个例子:
function printName() {
console.log("Dillion")
}
printName()
// Dillion
起初,调用堆栈是空的。声明时printName
,调用堆栈仍然是空的。待执行时printName()
,将其添加到调用栈中:
当函数完成时,它会从调用堆栈中删除。函数完成发生在:
-
函数中的所有行都已执行或
-
return
在函数中遇到语句
这样,调用堆栈可以跟踪当前正在执行的函数,以及从什么范围执行。
让我们看另一个例子:
function printName() {
function printFirstName() {
console.log("Dillion")
}
function printLastName() {
console.log("Megida")
}
printFirstName()
printLastName()
console.log("Dillion Megida")
}
printName()
// "Dillion"
// "Megida"
// "Dillion Megida"
在这个例子中,我们声明了一个名为printName
. 在这个函数中,我们声明了另外两个函数:printFirstName
和printLastName
。两个函数都被调用,然后是 statement console.log("Dillion Megida")
。
待执行时printName()
,将其添加到调用栈中:
中printName()
,什么时候printFirstName()
要执行,也加入到调用栈中(上图是printName()
因为还没有执行完):
完成执行后printFirstName()
(将“Dillion”记录到控制台),它会从调用堆栈中删除。printLastName()
现在要执行,所以它被添加到调用堆栈:
执行后printLastName()
(将“Megida”记录到控制台),它从调用堆栈中删除:
printName()
继续执行,现在执行console.log("Dillion Megida")
。您可以在控制台中看到“Dillion Megida”。printName()
现在完成,并从调用堆栈中删除:
这就是调用堆栈如何工作以跟踪当前正在执行的函数。但是调用堆栈有一个最大大小。当您拥有的函数多于调用堆栈中允许的数量时,您会收到超出最大调用堆栈大小的错误。
可以超过最大调用堆栈大小的一种常见情况是递归
我有一个
递归和调用栈
看看这个例子:
function printNames() {
console.log("Dillion")
printNames()
console.log("Megida")
}
printNames()
// "Dillion" - first call
// "Dillion" - second call
// "Dillion" - third call
// "Dillion" - fourth call
// "Dillion" - fifth call
// and so on, until max
在这个例子中,我们声明了printNames
. 在这个函数中,我们首先有console.log("Dillion")
,然后我们有printNames()
。
printNames()
让我们看看在函数声明之后调用会发生什么。
printNames()
--这被添加到调用堆栈:
console.log("Dillion")
被执行。“Dillion”被记录到控制台。然后printNames()
再次执行,将其作为活动函数添加到调用堆栈:
我们现在在调用堆栈中有两个函数。第一个调用printNames
和第二个调用printNames
是从第一个调用。
在该函数的第二次调用中,console.log("Dillion")
执行将“Dillion”记录到控制台。printNames()
然后再次执行该行。第三个调用,作为现在激活的函数被添加到调用栈中:
现在我们在调用堆栈中有三个函数。由于没有什么能阻止这些函数调用,它会无限地发生,直到“调用堆栈无法再接受它”?:
调用堆栈具有可同时容纳的函数的最大大小。此大小在不同的浏览器中可能会有所不同。当超过该大小时,您会收到最大调用堆栈大小超出错误。
这也会导致您的 JavaScript 应用程序无响应。而且您绝对不希望您的用户这样做。
在 JavaScript 程序中创建递归时,您还应该有一个基本情况,它会在某些调用后终止递归。这很重要,以免超过调用堆栈大小并导致应用程序崩溃。
这是一个基本案例的示例:
let counter = 0;
function printNames() {
console.log("Dillion")
counter++
if (counter < 5) {
printNames()
}
console.log("Megida")
}
printNames()
// Dillion
// Dillion
// Dillion
// Dillion
// Dillion
// Megida
// Megida
// Megida
// Megida
// Megida
我现在更新了代码以获得一个基本案例。这里的基本情况是“如果计数器变量不再小于 5,则停止递归”。所以当函数被调用时,printNames()
被添加到调用栈中。我们有counter
0,然后我们将“Dillion”记录到控制台。之后,我们加counter
1。然后我们有条件“如果计数器小于 5”。由于counter
小于 5,我们执行printNames()
。
现在调用堆栈有第一次和第二次printNames()
调用。
在第五次调用时printNames
,调用堆栈将有 5 次调用printNames()
:
请记住,前 4 个函数调用尚未完成。在他们完成之前,他们调用了另一个函数,该函数被添加到调用堆栈中。
在这第五次调用中,“Dillion”第五次记录到控制台。counter
,在 4,递增 1 到 5。然后检查条件“如果计数器小于 5”。counter
(as 5) 不小于 5,所以这是告诉递归停止的基本情况。
由于该函数不再调用自身,因此它继续执行下一行。
然后执行函数中的下一行,即console.log("Megida")
. 在这一行之后,调用堆栈中的第五个函数执行完毕,现在离开调用堆栈。
现在第五个函数已经执行完毕,第四个函数可以从它停止的地方继续。第四个函数的下一行是console.log("Megida")
在执行时将“Megida”记录到控制台。然后第四个函数离开堆栈:
堆栈上的剩余函数将执行完毕,直到堆栈变空:
包起来
当您遇到错误“超出最大调用堆栈”时,简单的意思是“调用 sack 现在包含的活动函数超出了它可以包含的数量”。
调用堆栈存储当前正在执行的一个或多个函数。当执行的函数过多时,调用堆栈可能会超出其大小,然后引发错误。这通常发生在没有基本情况的递归情况下。
(