很多文章中有提到:js的event loop中存在两种不同的task,microtask和macrotask,也有相应的事件队列,那么设计两种task的目的是什么?

单线程的javascript

多线程需要保存各线程的上下文,用户栈,内核栈等,并且要保证安全性,线程间共享数据时,需要加锁,

对于js这类动态类型的语言,实现多线程难度很高。

同理,PHP,Python等语言本质上都是单线程,虽然它们都通过某些手段实现了多线程。

另外,多线程最大的意义是并发,避免阻塞,提高CPU利用率,一般针对的是I/O操作。

然而JS中没有开销很大的I/O,所以实现多线程意义也不大。

尽管如此,浏览器环境中存在大量的DOM事件,网络请求,假设同一时刻,出现大量的事件需要处理,如果串行处理,一旦其中一个占用很长时间,其他操作将无法响应,因此需要一个机制来避免阻塞。

Event Loop

为了实现非阻塞引入了event loop机制。本质上是轮训任务队列。

运行时维护着一个任务队列,事件,网络请求,定时器,UI渲染,等等操作,一旦进入就绪状态就会进入任务队列。

比如一个网络请求完成时,它的回调函数会被放入任务队列等待调度,

另外还有一个执行栈,在每一轮loop中,从任务队列取出一个任务压入栈内执行。

整个过程是一个任务调度的流程,类似于操作系统的任务调度,所以为了更好的性能,会引入优先级机制,js的规范中并没有明确定义调度优先级的规则,但各家浏览器从实际应用的目的出发都会实现这个机制。

因此,实际上会有多个任务队列,按优先级或者其他角度区分,以提高效率。

两种不同的task

在w3c以及ES规范中,并没有明确定义microtask,因为这一点实际上是跟语言本身是无关的,本质上是任务调度的问题,具体如何实现需要从实际出发。

不过在WHATWG的规范中有明确microtask:

Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue.

每次event loop都会维护一个microtask队列,当前的loop执行栈空了以后,并且当前macrotask完成以后,会执行microtask队列,直到清空队列,然后进入下一个loop。

microtask的历史,在网上可以查找到的资料是跟 Mutation ObseversObject.observe 有关,可以认为microtask的提出是为了实现这两个api。

这两个api都是为了监听对象的变化,因此必须要等待所有的对象变更完毕之后才能执行,即一个macro任务执行完成以后,所以这也是为什么要另起一个microtask任务队列的原因。

从任务调度的角度来看,microtask提供了类似于协程的能力,比如nodejs中的 nextTick 或者浏览器中的 queueMicrotask