发布于

EventLoop

Authors
  • avatar
    Name
    田中原
    Twitter

EventLoop

总结:如何快速区分宏任务微任务

宏任务:定时器、或等待某些交互、信息交换完成的代码的回调属于宏任务

异步代码、交互操作、script代码

script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 环境)

微任务:

Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)

宏任务

浏览器Node
代码/script
交互事件
I/O(磁盘读写或网络通信)
setTimeout
setInterval
setImmediate
requestAnimationFrame
postMessage
MessageChannel

微任务

浏览器Node
Promise.then、catch、finally
Object.observe
process.nextTick
MutationObserver

事件循环

浏览器是多线程的,JS是单线程的(浏览器只分配一个线程来执行JS) 进程大线程小:一个进程中包含多个线程,例如在浏览器中打开一个HTML页面就占用了一个进程,加载页面的时候,浏览器分配一个线程去计算DOM树,分配其它的线程去加载对应的资源文件…再分配一个线程去自上而下执行JS。 同步:在一个线程上(主栈/主任务队列)同一个时间只能做一件事情,当前事情完成才能进行下一个事情(先把一个任务进栈执行,执行完成,在把下一个任务进栈,上一个任务出栈…) 异步:在主栈中执行一个任务,但是发现这个任务是一个异步的操作,我们会把它移除主栈,放到等待任务队列中(此时浏览器会分配其它线程监听异步任务是否到达指定的执行时间),如果主栈执行完成,监听者会把到达时间的异步任务重新放到主栈中执行

调用栈(Call Stack)

是一种后进先出的数据结构。当一个脚本执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入调用栈中,然后从头开始执行。

事件队列 (Task Queue)

js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起(其他模块进行处理),继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入到事件队列。

script是宏任务

  • 当我们第一次执行的时候,解释器会将整体代码script放入宏任务队列中,因此事件循环是从第一个宏任务开始的;

微任务追加

  • 如果在执行微任务的过程中,产生新的微任务添加到微任务队列中,也需要一起清空;微任务队列没清空之前,是不会执行下一个宏任务的。

async await属于微任务还是宏任务?

async await的本质是promise

await后的代码是微任务

异步任务优先级(同一次事件循环):微任务>宏任务

当执行栈中的任务清空,主线程会先检查微任务队列中是否有任务,如果有,就将微任务队列中的任务依次执行,直到微任务队列为空,之后再检查宏任务队列中是否有任务,如果有,则每次取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环

1.同步代码执行时推入(push)调用堆栈

2.异步代码由WebApi添加到异步队列

3.当调用堆栈为空时,将异步队列会推入堆栈

4.当回调添加到调用堆栈并执行,一旦执行完毕,该回调函数会被弹出(popped)调用堆栈

完整示例

const foo = () => console.log('First')

const bar = () => setTimeout(() => console.log('Second'), 500)

const baz = () => console.log('Third')

bar()

foo()

baz()

反馈一个小问题:

最近review代码时发现,同步的函数加了asnyc大家可以思考一下会不会导致错误?还有是否该避免这种写法?

优秀参考博文

掘金GIF图解析事件循环

Node.js事件循环官方博文