背景
如果你试图运行下面的代码:
function func(){
func()
}
func()
相信你会得到如下的结果
调用栈是用来管理函数调用关系的一种数据结构。让我们先来看看函数调用和栈结构。
函数调用
函数调用就是运行一个函数,是以在函数名后加小括号的方式调用函数。在变量提升的文章中,我们已经了解到在函数运行前会创建执行上下文,并且在每一次函数被调用是都会创建一个执行上下文。所以当函数调用另一个函数时,会产生多个执行上下文。
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { ... },
VariableEnvironment: { ... }
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { ... },
VariableEnvironment: { ... }
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: { ... },
VariableEnvironment: { ... }
}
...
此时JavaScript引擎就需要使用一种数据结构来管理这些执行上下文。
栈
栈是一种线性的、有序的数据集合,它的添加和删除操作只允许在集合的尾部进行,所以它具有先进后出的特点。
由于入栈和出栈操作在同一端处理,如果不停的入栈而不出栈的话,整个集合就会出现被占满的情况。
JavaScript调用栈
JavaScript引擎利用了栈这种数据结构来管理执行上下文的。函数被调用时向调用栈压入函数执行上下文,函数运行结束时函数执行上下文被弹出。所以文章开头的那段代码会形成如下的调用栈。
只有入栈而没有出栈,直到调用栈中的执行上下文达到最大数量,就会报错结束。
在开发过程中,利用浏览器控制台工具可以很好的观察JavaScript调用栈的信息,帮助我们更好的调试程序。 我们在之前的代码中加入断点。
运行第一次。
跳过一次断点后,可以明显的看出调用栈的变化。
总结
- JavaScript调用栈使用栈数据结构管理着执行上下文。执行上下文随着函数的调用而入栈,随着运行结束而出栈。
- 调用栈满时会报错。
- 当需要使用递归时,可考虑使用迭代替换以减少执行上下文的数量。
当我们在实际开发中,势必会使用很多第三方库或框架,当发生报错时可利用控制台的调用栈工具快速定位问题。
对于开发者工具的使用,后面会有单独的章节讲解。