JavaScript调用栈
为了管理执行上下文,JavaScript引擎使用调用栈。JS调用栈的工作是在内部执行的,但是我们在这里将了解它的工作原理。
在本节中,我们将讨论JavaScript调用栈及其工作原理。我们还将讨论一个示例,以帮助我们更好地理解这个概念。
什么是JS调用栈
JavaScript执行上下文(全局执行上下文和函数执行上下文)通过JavaScript引擎执行。为了管理这些执行上下文,JS引擎使用调用栈。因此,JS调用栈是一个数据结构,用于跟踪被调用和执行的函数的信息。因此,如果用户调用一个函数进行执行,指定的函数将被推入/添加到调用栈中,当用户从函数返回时,意味着该函数从调用栈中弹出。因此,调用栈是一个遵循栈的顺序原则(即后入先出)的普通栈数据结构。
JavaScript调用栈的作用
JS引擎在以下情况下使用调用栈:
- 当用户执行任何脚本时,JS引擎创建一个全局执行上下文,然后将其添加到调用栈的顶部,以便执行。
- 当调用任何函数时,JS引擎创建一个函数执行上下文,并将其添加到栈的顶部,以便执行被调用的函数。
- 如果一个函数调用另一个函数,JS引擎为被调用的函数创建一个函数执行上下文,将其添加到栈的顶部,并开始执行。
- 当任何函数执行完成时,JS引擎将其从栈中弹出,并继续执行栈中存储的其他函数。
- 如果栈中没有剩余空间,并且我们尝试推入更多的函数,则会抛出“堆栈溢出”错误,如果调用栈中没有其他执行上下文,则会抛出“堆栈下溢”错误。
JavaScript调用栈示例
让我们看一个示例来理解JavaScript调用栈函数的用法:
function getSum(x, y) {
return x+ y;
}
function findavg(x,y) {
return getSum(x,y) / 2;
}
let z = findavg(10, 20);
代码如何工作
在上述代码中,我们创建了两个函数getSum()和findavg(),脚本的执行按照以下步骤进行:
- 当脚本执行开始时,JS引擎首先创建一个全局执行上下文(即global()函数)并将其添加到调用栈的顶部。
- 全局执行进入执行阶段后,会从创建阶段进入执行阶段,详细如下图所示:
-
调用findavg(10, 20)函数,因此JS引擎会为其创建函数执行上下文,并将其推入调用栈的顶部。
-
现在,在调用栈中推入了两个函数,即global()和findavg(),栈的顶部是findavg()函数,如下图所示:
-
JS引擎开始执行findavg()函数,因为它位于栈的顶部,如下图所示:
-
代码中,getSum()函数是在findavg()函数定义内部被调用的,因此JS引擎为getSum()函数创建函数执行上下文,并将其推到栈的顶部。
-
现在,栈中有三个函数,即global()、findavg()和getSum()函数,如下图所示:
-
因此,JS引擎首先执行getSum()函数并将其从调用栈弹出。
-
类似地,findavg()函数执行完毕并从调用栈弹出。
-
由于两个函数的执行都完成了,并且调用栈中没有其他需要执行的函数,JS引擎停止调用栈的执行并继续执行其他任务。
何时调用栈溢出
溢出情况发生在调用栈中没有剩余空间,或者可能发生在递归函数没有退出点的情况下。JavaScript调用栈的大小是固定的,根据主机环境的实现(即Node.js或Web浏览器)而不同。因此,当超出了栈的定义大小限制时,就会发生栈溢出。于是,它会抛出一个栈溢出错误。
示例:
下面的示例描述了栈溢出的情况:
function test(){
test();
}
test();
所以,在上面的代码中,我们可以看到我们递归调用了test()函数,这意味着这个函数会一直执行,直到主机环境超过最大调用大小,因此堆栈会抛出堆栈溢出错误。
需要注意的一点是:
JavaScript是一种同步和单线程的编程语言。这意味着当任何脚本执行时,JS引擎会按照从上到下的顺序逐行执行代码。因此,JavaScript引擎只有一个调用堆栈,并且一次只能做一件事情。