- 发布于
EventLoop
- Authors
- Name
- 田中原
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
大家可以思考一下会不会导致错误?还有是否该避免这种写法?