前言
堆栈的概念对于我来说是熟悉又陌生的感觉,之前看过很多相关的文章及写过一些demo可后面用不到后,就慢慢淡忘了,今天要好好理解并记录下
JavaScript 引擎
Google V8引擎是一个比较流行的javascript引擎示例,该引擎包括两个主要组件:
- Memory Heap内存堆 — 这是内存分配的地方
- call stack调用堆栈 — 这是你代码执行时栈帧存放的位置
RunTime 运行时

Call Stack 调用堆栈
JavaScript 是一种单线程编程语言,这意味着它只有一个 Call Stack 。因此,它一次仅能做一件事。
Call Stack 是一个数据结构,它基本上记录了我们在程序中的所处的位置。如果我们进入一个函数,我们把它放在堆栈的顶部。如果我们从一个函数中返回,我们弹出堆栈的顶部。这是所有的堆栈可以做的东西。
我们看一个示例:
function multiply(x,y){
return x * y
}
function printSqure(x){
var s = multiply(x,x)
console.log(s)
}
printSqure(5)
当引擎开始执行这个代码时,Call Stack 将会变成空的。之后,执行的步骤如下:

Call Stack 的每个入口被称为 Stack Frame(栈帧)。
这正是在抛出异常时如何构建 stack trace 的方法 - 这基本上是在异常发生时的 Call Stack 的状态。看看下面的代码:
function foo(){
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar(){
foo()
}
function start() {
bar();
}
start();
如果这是在 Chrome 中执行的(假设这个代码在一个名为 foo.js 的文件中),那么会产生下面的 stack trace:
Blowing the stack—当达到最大调用堆栈大小时,会发生这种情况。这可能会很容易发生,特别是如果你使用递归,而不是非常广泛地测试你的代码。看看这个示例代码:
function foo() {
foo();
}
foo();
当引擎开始执行这个代码时,它首先调用函数“foo”。然而,这个函数是递归的,并且开始调用自己而没有任何终止条件。所以在执行的每个步骤中,同一个函数会一次又一次地添加到调用堆栈中。它看起来像这样:

然而,在某些情况下,调用堆栈中函数调用的数量超出了调用堆栈的实际大小,浏览器通过抛出一个错误(如下所示)来决定采取行动:

在单线程上运行代码可能非常容易,因为你不必处理多线程环境中出现的复杂场景,例如死锁。
但是在单线程上运行也是非常有限的。由于JavaScript只有一个调用堆栈,所以当事情很慢时会发生什么?
并发与事件循环
如果在调用堆栈中执行的函数调用需要花费大量时间才能进行处理,会发生什么? 例如,假设你想在浏览器中使用 JavaScript 进行一些复杂的图像转换。
你可能会问 - 为什么这会是一个问题?问题是,虽然调用堆栈有要执行的函数,浏览器实际上不能做任何事情 - 它被阻塞了。这意味着浏览器无法渲染,它不能运行任何其他代码,它就是被卡住了。如果你想在你的应用程序中使用流畅的 UI ,这就会产生问题。
而且这并不是唯一的问题。一旦你的浏览器开始在 Call Stack 中处理过多的任务,它可能会停止响应相当长的时间。大多数浏览器会通过触发错误来采取行动,询问你是否要终止网页。

所以,这并不是最好的用户体验,对吗?
那么,我们如何执行大量代码而不阻塞 UI 使得浏览器无法响应? 解决方案就是异步回调。
这将在“ JavaScript 工作原理”教程的第2部分中更详细地解释:“V8 引擎内部+关于如何编写优化代码的5个技巧”。
同时,如果你在 JavaScript 应用程序中难以复现和理解问题,请查看 SessionStack 。 SessionStack 会记录你的 Web 应用中的所有东西:所有的 DOM 更改、用户交互、JavaScript 异常、堆栈跟踪、网络请求失败、调试消息等。
通过 SessionStack ,你可以以视频的方式重现问题,并查看发生在用户身上的所有事情。
总结
虽然JavaScript是单线程,但Event Loop是javascript的执行机制,可以很好的处理事件,它也分为同步与异步,当为同步时JavaScript的顺序必须一个完成之后才会完成下一个,如果前面出现无限循环后就会出现爆栈,导致后面的无法执行;而异步时又分为宏观任务(script,setTimeout,setInterval)及微观任务(Promise,process.nextTick)进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务