JS 中微任务与宏任务与事件循环(Event Loop)
单线程的 JS
大家应该都知道 JS 有一个特性,就是 单线程。
JS 的主要用途是与 用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JS 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 设备(输入输出设备)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。
JS 语言的设计者意识到,这时主线程完全可以不管 IO 设备,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有的任务可以分成两种,一种是 同步任务(synchronous),另一种是 异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入 "任务队列"(task queue) 的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
所以,是 单线程的出现,才引发了同步和异步的出现,我们可以看下面的例子,方便大家 更好理解同步和异步
参考于 阮一峰 JavaScript 运行机制详解:再谈 Event Loop
现实生活中的同步和异步
比方说我们吃 KFC,我们都要去收银台排队(假设不能进行扫码点餐),假设我们进行点餐+付款+取餐需要十分钟,这个时候店员说,按照店内的规定,我们只能一个接一个的服务客户,后面的客户需要等待前面客户点餐完成,付款完成,取餐完成,才能进行点餐,付款,取餐。这种情况就是所谓的同步,就是按顺序执行,一件事情做完了,才能做下一件事情。
这种结果导致最后的问题就是效率太过于低下,因为店员需要一个一个的进行服务,如果顾客很多,那么店员就需要等待很久才能服务下一个顾客。
为了提升服务效率,KFC 推出了 点餐区 以及 取餐区。在你付款完毕以后,给你一张取餐码,那么你就可以从点餐区的队列中离开,下一个客户就可以接着点餐,而你只需要等前台通知,你要的套餐做好了,快来取餐区取餐。
JS 中的同步与异步
同步
任务从上往下按顺序执行,只有前一个任务执行完毕,才能执行后一个任务(后一个点餐的客户必须等前一个客户取餐完毕)。
异步
前一个任务还没执行完毕(未取餐),也没任何关系,直接执行下一个任务(下一个客户点餐),等到前台通知取餐,在执行(取餐)。
经过同步任务和异步任务的划分,程序的运行效率明显提高
JS 中的宏任务与微任务
上面已经对同步任务和异步任务进行了划分,我们都知道,同步任务就是按顺序从上往下依次执行。
异步任务也是有它的执行顺序的,它也是 从上往下,但是,异步任务里,对于异步类型还有进一步的划分,分为 宏任务 和 微任务,微任务比宏任务先执行。
微任务(micro-task)
process.nextTick、Promise、MutationObserver 等
宏任务(macro-task)
setTimeout、setInterval、setImmediate、I/O、UI rendering 等
值得注意的是,Promise 是有一点特殊的,它既可以是同步的,也可以是异步的,具体是同步还是异步,取决于 Promise 的构造函数中是否传入了 回调函数,如果传入了回调函数,那么 Promise 就是同步的,否则就是异步的。
new Promise(function (resolve) {
console.log(1);
});上面这段代码,Promise 的回调函数是同步的,所以 Promise 是同步的,所以 1 会先打印出来。
学会如何区分微任务与宏任务之后,我们也就对异步任务的执行顺序划分有了进一步的了解。
事件循环(Event Loop)
初始状态下,调用栈空。微任务队列空,宏任务队列里只有一个 script(整体代码)。这时首先执行并出队的就是 script(整体代码)。
整体代码作为宏任务进入调用栈,进行同步任务和异步任务的区分。
同步任务 直接执行 并且在执行完之后 出栈,异步任务进行微任务和宏任务的划分,分别被推入 微任务队列 和 宏任务队列。
等同步任务执行完(调用栈为空)以后,在处理微任务队列,将微任务队列压入调用栈
当调用栈中的微任务队列被处理完毕(调用栈为空)之后,再将宏任务队列压入调用栈,直至调用栈再一次为空,一次轮回结束。
整体的运行流程可以查看下图,红色箭头为主要的执行流程,整体代码(宏任务) => 同步任务 => 异步任务 => 微任务队列 => 宏任务队列

关于这个 Event Loop,其实涉及了很多的知识点,包括 微任务,宏任务,调用栈,执行上下文 ,同步与异步 ,任务队列 。