天天看点

搞懂 JavaScript Event Loop

搞懂 JavaScript Event Loop

作者:UC 国际研发 阿定

Event Loop,事件循环,是贯穿 JavaScript 整个执行时的关键,这篇文章将通过理论+实践的方式,带你进一步了解这个神奇的机制。

理论篇

搞懂 JavaScript Event Loop

(图片来源于 MDN)

上图比较形象地概括了 JavaScript 在运行时的理想情景,下面逐一介绍。

函数调用栈 Stack

Stack,顾名思义,是一个 FILO(First In Last Out) 的结构。每次调用函数,在系统中就会把当前函数以及函数内的变量压栈,举个栗子:

搞懂 JavaScript Event Loop

上面代码,定义了两个函数对象,并且执行了一条语句 test(1),试行时, test 函数入栈,进入到 test 函数的执行上下文,发现 return test2(n),再把 test2 函数入栈,最后 test2 返回结果,test2 函数出栈,test 出栈,输出结果:124。

搞懂 JavaScript Event Loop

堆内存 Heap

堆内存中存放的是对象,即引用类型的变量,保存的是实际的内容,而变量名仍然是保存在上面说的 stack 内存中,所以当我们操作变量名,比如:

搞懂 JavaScript Event Loop

实际上,做的是,在栈内存中声明了一个变量 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。下一循环又重复这个过程。

举个栗子:

搞懂 JavaScript Event Loop

上面一段代码,在引擎解析后完成后,就会变成这样,如图所示:

搞懂 JavaScript 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

搞懂 JavaScript 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会不一样。

实战篇

搞懂 JavaScript Event Loop
搞懂 JavaScript Event Loop

还是画图辅助:

第一轮 Event Loop:

搞懂 JavaScript Event Loop

1、主流程输出:1, 4, 7

2、执行第一个 Micro event queue:输出 3

3、第二个 Micro event queue:输出 5

4、Micro event queue 清空,第一轮执行完毕

第二轮 Event Loop:

搞懂 JavaScript Event Loop

1、主流程输出 2

2、Micro event queue 为空,第二轮执行完毕

第三轮 Event Loop:

搞懂 JavaScript Event Loop

1、主流程输出 6

2、第二轮执行完毕

第四轮 Event Loop:

搞懂 JavaScript Event Loop

1、注意,这里执行输出 8 后,resolve,这时才向 Micro event queue 压入 then 回调

2、执行 then9 回调,输出 9

3、又有新的 setTimeout,压入 Macro event queue

4、这轮循环没有东西可执行,结束

第五轮 Event Loop:

搞懂 JavaScript 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
           

继续阅读