js在浏览器中的event loop事件队列示例详解
前言
以下内容是js在浏览器中的事件队列执行,与在nodejs中有所区别,请注意。
都说js是单线程的,不过它本身其实不是单线程,但是在浏览器中执行时只分配一个线程进行执行。
所以说js执行是单线程的,一次只能进行一项任务,就是一件任务一件任务做,做完一件做下一件。
认识一个栈两个队列
一个调用栈Stack。
一个宏队列,macrotask,也叫tasks。
一个微队列,microtask,也叫jobs。
执行过程
js就是执行全局Script同步代码,这中间碰到一些异步任务先加进对应的队列。
做完之后,调用栈就为空了。
然后将队列(先微队列后宏队列)里面的首个任务提到调用栈来做,一件一件做完直到队列中的任务都做完。
总结就是,先做同步的任务,再做微队列的任务,再做宏队列的任务。
异步任务怎么分配
这些异步任务包括但不限于:
以下分配给宏队列:
setTimeout
setInterval
requestAnimationFrame
I/O
UI rendering
以下分配给微队列:
Promise
Object.observe
MutationObserver
常见的宏队列:setTimeout,常见的微队列:Promise。
简单例子
console.log("同步任务1"); setTimeout(() => { console.log("宏任务"); }); new Promise((resolve, reject) => { console.log("同步任务2"); resolve("微任务"); }).then((data) => { console.log(data); }); console.log("同步任务3");
结果是(按标号加任务,按箭头执行):
需要注意的是Promise的第一层没有执行回调之前是同步的,也就是上面的同步任务2。
难一点的例子
console.log("同步任务1"); console.log("同步任务2"); new Promise((resolve, reject) => { console.log("同步任务3"); setTimeout(() => { console.log("宏任务1"); Promise.resolve() .then(() => { console.log("微任务5"); }) .then(() => { console.log("微任务6"); }); }); resolve("微任务1"); }) .then((data) => { console.log(data); return "微任务3"; }) .then((data) => { console.log(data); }); setTimeout(() => { console.log("宏任务2"); }, 0); new Promise((resolve, reject) => { resolve("微任务2"); }) .then((data, resolve) => { console.log(data); return "微任务4"; }) .then((data) => { console.log(data); }); console.log("同步任务4");
如何看呢,先看第一层,红色代表同步,绿色微任务,蓝色宏任务。
我们会把同步任务执行完,然后看见微任务有俩,宏任务也有俩。
本来的执行顺序可能是这样(我这里按照序号来表达顺序了,请和简单例子区分开来):
但是没那么顺利,执行到标号6时不一样了。
因为微任务执行过程中可能会产生新的微任务。
上面的微任务1执行完会把微任务3加在微任务2后面,也就是微任务2执行完也轮不到宏任务,会继续执行新的微任务直到微任务队列暂时为空。
所以接下来会按照加入队列的顺序执行完四个微任务,这时候发现没有新的微任务产生,才开始执行宏任务:
但是需要注意的是,上面执行到标号5
时又不一样了,宏任务一执行后又产生了新的微任务,所以宏任务两个并没有顺利连续执行,而是被插入的微任务拦住了。
(要记住微任务与宏任务队列都存在时一定是微任务先执行完再来执行宏任务,即使是宏任务执行产生的微任务也同理)
所以最后的答案,如果存在不理解的,可以在认真回顾一下上文: