Event Loop、 宏任务和微任务

本文将介绍我自己对JS Event Loop宏任务、微任务的理解。
二话不说先上图:

接下来将会针对此图讲解什么是Event Loop 什么事宏任务和微任务(其实聪明的你们通过图大体也能了解的是吧~),再此之前先简单介绍几个概念。

为何js是单线程

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出 Web Worker 标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。(此段来自阮老师)

概念

  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
  • 异步任务:不进入主线程、而进入任务队列(Task Queue)的任务。
  • 任务队列:是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在”任务队列”中添加一个事件,表示相关的异步任务可以进入”执行栈”了。主线程读取”任务队列”,就是读取里面有哪些事件。

详解图示

  1. 当一段代码在主线程执行时,会有同步任务和异步任务,异步任务会判断是微任务还是宏任务,进行不同处理。然后继续执行当前主线程内的代码,直到结束。此段主线程执行的的为宏任务
  2. 接着上面的判断,宏任务的异步操作会先进入Event Table执行,然后当执行结束会把回调函数推入到Event Queue ,然后等待主线程的结束。对于微任务的异步会推入到另外一个Event Queue ,等待主线程的结束。
  3. 当主线程结束后,先执行所有的微任务,然后执行把宏任务的任务队列(Event Queue)中的event拉到主线程执行。
  4. 一直循环前面的操作。至此你应该懂的EventLoop了。
    备注:如果在执行 microtask(微任务) 的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行。

举个例子🌰

setTimeout(function() {
    console.log('setTimeout');
}, 0)

new Promise(function(resolve) {
  console.log('promise 1');
  resolve();
  console.log('promise 2')
}).then(function() {
    console.log('promise then');
})

console.log('1');

输出结果:promise 1 、promise 2 、1 、promise then 、setTimeout
解释

  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,则进入Event Table执行并注册,然后将其回调函数发到宏任务Event Queue。
  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。里面的console.log(),立即执行。
  • 继续,遇到console.log(),立即执行。
  • 至此这个主线程的宏任务执行结束,接着查找微任务,发现then的回调然后执行。
  • 第一轮事件循环结束了,我们开始第二轮循环,从宏任务Event Queue开始执行。即宏任务Event Queue中setTimeout对应的回调函数,立即执行。
  • END。

为何会有宏任务和微任务的说法。

eventloop中处理微任务和宏任务的逻辑也是不同厂商按照规范实现的 jsvm。

补充几个宏任务和微任务

宏任务 浏览器 Node
setTimeout
setInterval
setImmediate x
requestAnimationFrame x
微任务 浏览器 Node
process.nextTick x
MutationObserver x
Promise.then catch finally

来自我的简书的文章:Event Loop、 宏任务和微任务