JavaScript执行机制 - 调用栈

236 阅读2分钟

如果你试图运行下面的代码:

function func(){
    func()
}
func()

相信你会得到如下的结果

调用栈是用来管理函数调用关系的一种数据结构。让我们先来看看函数调用栈结构

函数调用

函数调用就是运行一个函数,是以在函数名后加小括号的方式调用函数。在JavaScript执行机制 - 变量提升的文章中,我们已经了解到在函数运行前会创建执行上下文,并且在每一次函数被调用是都会创建一个执行上下文。所以当函数调用另一个函数时,会产生多个执行上下文

GlobalExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: { ... },
  VariableEnvironment: { ... }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: { ... },
  VariableEnvironment: { ... }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: { ... },
  VariableEnvironment: { ... }
}

...

此时JavaScript引擎就需要使用一种数据结构来管理这些执行上下文

栈是一种线性的有序的数据集合,它的添加和删除操作只允许在集合的尾部进行,所以它具有先进后出的特点。

由于入栈和出栈操作在同一端处理,如果不停的入栈而不出栈的话,整个集合就会出现被占满的情况。

JavaScript调用栈

JavaScript引擎利用了这种数据结构来管理执行上下文的。函数被调用时向调用栈压入函数执行上下文函数运行结束时函数执行上下文被弹出。所以文章开头的那段代码会形成如下的调用栈。

只有入栈而没有出栈,直到调用栈中的执行上下文达到最大数量,就会报错结束。

在开发过程中,利用浏览器控制台工具可以很好的观察JavaScript调用栈的信息,帮助我们更好的调试程序。 我们在之前的代码中加入断点。

运行第一次。

跳过一次断点后,可以明显的看出调用栈的变化。

总结

  • JavaScript调用栈使用栈数据结构管理着执行上下文。执行上下文随着函数的调用而入栈,随着运行结束而出栈。
  • 调用栈满时会报错。
  • 当需要使用递归时,可考虑使用迭代替换以减少执行上下文的数量。

当我们在实际开发中,势必会使用很多第三方库或框架,当发生报错时可利用控制台的调用栈工具快速定位问题。

对于开发者工具的使用,后面会有单独的章节讲解。