作者:UC 国际研发 阿定
Event Loop,事件循环,是贯穿 JavaScript 整个执行时的关键,这篇文章将通过理论+实践的方式,带你进一步了解这个神奇的机制。
理论篇
(图片来源于 MDN)
上图比较形象地概括了 JavaScript 在运行时的理想情景,下面逐一介绍。
函数调用栈 Stack
Stack,顾名思义,是一个 FILO(First In Last Out) 的结构。每次调用函数,在系统中就会把当前函数以及函数内的变量压栈,举个栗子:
上面代码,定义了两个函数对象,并且执行了一条语句 test(1),试行时, test 函数入栈,进入到 test 函数的执行上下文,发现 return test2(n),再把 test2 函数入栈,最后 test2 返回结果,test2 函数出栈,test 出栈,输出结果:124。
堆内存 Heap
堆内存中存放的是对象,即引用类型的变量,保存的是实际的内容,而变量名仍然是保存在上面说的 stack 内存中,所以当我们操作变量名,比如:
实际上,做的是,在栈内存中声明了一个变量 b,并把 b 指向了 a 所指向的堆内存中的那个对象。
异步队列 Event Queue
因为 JS 单线程的特性,实际上很多需要耗时的任务,JS 都会把异步任务都放到 queue 中,在主线程的事情做完后,会定期的轮询 queue,把里面的结果拿出来,这样循环往复的过程就构成了 Event Loop。
Event Loop
实际上,对于异步任务来说,不是所有的任务的生产都是一样的。这里有两个重要的概念 macrotasks 和 microtasks。这两种 task 占据了不同的 queue(Macro event queue 和 Micro event queue),并且这两种任务不会混在一起执行。为什么呢?因为对于每一轮的 Event Loop 来说,会取出 Macro event queue 中的一个 task 出来执行,完成这个 marcotask 后,会从 Micro event queue 取出所有 microtasks 执行,至此完成一轮 Event Loop。下一循环又重复这个过程。
举个栗子:
上面一段代码,在引擎解析后完成后,就会变成这样,如图所示:
1、首先执行同步任务,按出现顺序,输出 1
2、遇到 setTimeout,放入Macro event queue
3、遇到 process,放入 Micro event queue
4、遇到 promise,先立即执行,输出 4,并将 then 回调放入 Micro event queue
5、然后看 Micro event queue,逐个执行,输出 3, 输出 5
6、第一轮 Event Loop 执行结束
然后开始第二轮 Event Loop
1、取出 Macro event queue 第一个放入主流程执行
2、输出 2
3、Micro event queue 没有任务
4、第二轮 Event Loop 执行结束
Macrotasks & Microtasks
Macro:
setTimeout, setInternal, setImmediate, I/O tasks
Micro:
process.nextTick, Promises
引擎标准
实际上,对于怎么处理 macrotasks 和 microtasks 完全取决于引擎。所以说,同样的代码,在 Chrome 执行的输出效果,也许跟 Firefox, Safari会不一样。
实战篇
还是画图辅助:
第一轮 Event Loop:
1、主流程输出:1, 4, 7
2、执行第一个 Micro event queue:输出 3
3、第二个 Micro event queue:输出 5
4、Micro event queue 清空,第一轮执行完毕
第二轮 Event Loop:
1、主流程输出 2
2、Micro event queue 为空,第二轮执行完毕
第三轮 Event Loop:
1、主流程输出 6
2、第二轮执行完毕
第四轮 Event Loop:
1、注意,这里执行输出 8 后,resolve,这时才向 Micro event queue 压入 then 回调
2、执行 then9 回调,输出 9
3、又有新的 setTimeout,压入 Macro event queue
4、这轮循环没有东西可执行,结束
第五轮 Event Loop:
1、第五轮,setTimeout10 进入主流程,输出 10
2、遇到 promise,输出 11
3、resolve, 压入 then 到 Micro event queue
4、取出 Micro event queue 执行,输出 12
5、完毕
至此, 这段代码的完整执行流程就结束了,最终输出:
1, 4, 7, 3, 5, 2, 6, 8, 9, 10, 11, 12