JS的event loop是常考常用的知识点
前置知识
JS虽然是单线程的,但是浏览器(宿主环境)是多线程的:
- JS主线程:与GUI线程互斥
- GUI线程:界面渲染和绘制、解析HTML、CSS、构建DOM树和render树
- 定时器线程
- http请求线程
- 等等
在浏览器中不同的异步操作由不同的的浏览器内核调度执行,比如setTimeout由定时器线程处理,界面的渲染由GUI线程处理,不同的异步操作添加到任务队列的时机不同。
JS的事件循环机制
- 首先,同步任务进入
主线程,异步任务先进入event table - 异步任务时机成熟后会进入
event queue事件队列 - 主线程执行完成后会去查看事件队列是否有任务在排队,有就推入主线程
以上过程往复循环就是event loop
异步任务还有细分
分类
异步任务还可以分为宏任务和微任务,在js中两者执行的优先级不同,微任务为js引擎处理,宏任务由宿主环境处理
常见的宏任务:
- setTimeout setInterval
- setImmediate
- script代码块
常见的微任务:
- Promise.then() catch()
- Async/await
- I/O
过程
js的同步任务会形成一个执行栈,也是一个队列,宏任务会进入宏任务队列,微任务进入微任务队列,执行栈清空后,会先去看微任务队列,将微任务依次加到执行栈末尾,直到微任务任务全部清空,然后再去检查宏任务队列,并将宏任务加入执行栈,最终执行栈完全清空
好几个注意点提前说:
setTimeout | setInterval优先级高于setImmediate- 创建Promise对象时,Promise
构造函数中的语句是同步执行,then/catch的回调是异步的 - 就算setTimeout时间间隔设置为0也是宏任务
- 执行栈清空后,会先push
一个微任务进入执行栈中,此时如果产生了新的微任务,继续推入微任务队列,执行栈清空后,继续push微任务到执行栈,直到微任务队列清空。
实战演练
console.log("1")
setTimeout(()=>{
console.log("2")
Promise.resolve().then(()=>{
console.log("3")
})
},0)
setTimeout(()=>{
console.log("4")
Promise.resolve().then(()=>{
console.log("5")
})
},0)
const p = new Promise((resolve,reject)=>{
console.log("6")
resolve("7")
})
p.then((data)=>{
console.log(data)
})
console.log("8")
js从上往下执行一遍会形成如下图所示的分配情况:
-
按照event loop机制首先会清空执行栈,打印 1 6 8
-
然后检查微任务队列,推入执行栈执行,打印 7
-
微任务清空后,检查宏任务队列,此时堆栈状态更新为下图所示,开启新的循环
- 执行栈中有内容,先打印 2,执行栈清空
- 然后检查微任务,推入执行栈,打印 3 微任务队列清空
- 另一个setTimeout也是同样
- 最终打印顺序为1 6 8 7 2 3 4 5