event loop 是一个执行模型,咱们这里可以简单的理解为就是js代码的执行模型。浏览器和NodeJS都实现了自己的event loop。
- 浏览器的event loop 是在html5规范中明确定义的。
- NodeJS的是基于libuv实现的,可以参考libuv的文档或者nodejs的文档。 libuv已经对event
- loop做出了实现,而在html5规范中,只是定义了浏览器的event loop模型,具体实现交给了浏览器厂商。
**浏览器的eventloop执行模型:**宏任务队列和微任务队列
以下这些异步任务会进入宏任务队列
- setTimeout
- setInterval
- setImmediate (Node独有)
- requestAnimationFrame(浏览器独有)
- I/O
- UI rendering (浏览器独有)
以下异步任务会进入微任务队列micro task queue
- process.nextTick (Node独有)
- Promise(new promise 是同步代码)
- Object.observe
- MutationObserver
浏览器的Event Loop描述图,下图也是浏览器中js执行栈的描述
浏览器中event loop的执行过程:
- stack 堆栈,所有的同步/异步代码都在这里,首先从上往下执行同步代码,遇到异步代码都会放到Background
threads(后台程序)中。 当stack执行完毕后,stack会被全部清空。 这时候会依次执行为任务Microtask - Queue,直到微任务被执行完毕。如果微任务中包含微任务,会自动添加到队列末尾,这个周期内也会被执行。 这时候microtask
- queue 和stack都是空,取出task queue位于队首的任务,执行完毕清空stack。继续执行步骤3。 重复3-4步骤
…
这里归纳3个重点:
- 同步代码执行完成后,会先执行微任务队列。注意:此时会把所有微任务队列全部执行完。再去宏队列macrotask取出一个执行,如果宏任务执行过程中产生新的微任务,会跳过微任务继续执行并立即把微任务放到异步任务队列大池子中。当前条宏任务执行完后,又去检查待执行的任务队列大池子,如果有微任务,就优先执行微任务,如果没有就继续执行宏任务队列;
- 微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;
- 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。
看个例子:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
结果:1475236
总结:
- new promise()是一个常规macrotask宏任务,.then()是microtask微任务,代码会依次执行。
在执行微队列microtask - queue中任务的时候,如果又产生了microtask,那么会继续添加到队列的末尾,也会在这个周期执行,直到microtask
queue为空停止。 - 注:当然如果你在microtask中不断的产生microtask,那么其他宏任务macrotask就无法执行了,但是这个操作也不是无限的,拿NodeJS中的微任务process.nextTick()来说,它的上限是1000个。
NodeJS的eventloop模型:
NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:
各个阶段执行的任务如下:
- timers阶段:这个阶段执行setTimeout和setInterval预定的callback I/O
- callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
- idle, prepare阶段:仅node内部使用
- poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
- check阶段:执行setImmediate()设定的callbacks close
- callbacks阶段:执行socket.on(‘close’, …)这些callbacks
NodeJS中宏队列主要有4个
由上面的介绍可以看到,回调事件主要位于4个macrotask queue中:
- Timers Queue
- IO Callbacks Queue
- Check Queue
- Close Callbacks Queue
这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。
NodeJS中微队列主要有2个:
- Next Tick Queue:是放置process.nextTick(callback)的回调任务的 Other
- Micro Queue:放置其他microtask,比如Promise等
在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。
具体可以通过下图加深一下理解:
大体解释一下NodeJS的Event Loop过程:
- 执行全局Script的同步代码 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other
Microtask Queue中的所有任务 - 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event
- Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2 Timers
- Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close
- Callback Queue -> 步骤2 -> Timers Queue …
这就是Node的Event Loop
setTimeout 和 setImmediate 的执行顺序是不确定的,当大家真正使用到的时候再深入了解吧。
setTimeout 对比 setImmediate
- setTimeout(fn, 0)在Timers阶段执行,并且是在poll阶段进行判断是否达到指定的timer时间才会执行
- setImmediate(fn)在Check阶段执行
两者的执行顺序要根据当前的执行环境才能确定:
- 如果两者都在主模块(main module)调用,那么执行先后取决于进程性能,顺序随机
- 如果两者都不在主模块调用,即在一个I/O Circle中调用,那么setImmediate的回调永远先执行,因为会先到Check阶段
setImmediate 对比 process.nextTick - setImmediate(fn)的回调任务会插入到宏队列Check Queue中
- process.nextTick(fn)的回调任务会插入到微队列Next Tick Queue中
- process.nextTick(fn)调用深度有限制,上限是1000,而setImmedaite则没有
总结 - 浏览器的Event Loop和NodeJS的Event Loop是不同的,实现机制也不一样,不要混为一谈。
- 浏览器可以理解成只有1个宏任务队列和1个微任务队列,先执行全局Script代码,执行完同步代码调用栈清空后,从微任务队列中依次取出所有的任务放入调用栈执行,微任务队列清空后,从宏任务队列中只取位于队首的任务放入调用栈执行,注意这里和Node的区别,只取一个,然后继续执行微队列中的所有任务,再去宏队列取一个,以此构成事件循环。
- NodeJS可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next
- Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask
- Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
- MacroTask包括: setTimeout、setInterval、
setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering - Microtask包括:
process.nextTick(Node)、Promise、Object.observe、MutationObserver