金秋飒爽,丹桂飘香,我们又迎来了一年一度金九银十招聘季,有一个问题也是经常要cue到,逢人就要问:你知道EventLoop吗,展开说说?
Event Loop ,“事件循环”
浏览器 or Node的一种解决JavaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
JS有一个主线程main thread
和调用栈call-stack
,所有的任务哦度会被放到调用栈里等待主线程执行。
采用后进先出
的原则, 当函数执行的时候,会被添加到栈的顶部,当操作完成后,就会从栈顶移除,直到栈内被清空。
JS单线程任务分为:同步任务 and 异步任务
同步任务:在调用栈中按顺序等待主线程依次执行
异步任务:在异步任务有结果后,将注册的回调函数放入任务队列
中等待主线程空闲,被读取到栈内等待执行
任务队列
Task Queue
:队列,先进先出
执行栈完成同步任务后,查看执行栈是否为空,如果执行栈为空,就会检查微任务队列是否为空,如果是空的话,执行宏任务,否则就一次性执行完所有的微任务。
举个例子🌰
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
第一次执行
执行同步代码,将宏任务和微任务划分到各自队列中
Tasks宏任务:run script、 setTimeout callback
Microtasks微任务:Promise then
JS stack: script
Log: script start、script end。
第二次执行
执行宏任务后,检测到微任务队列中不为空,执行Promise1,执行完后调用Promise2.then,放入到微任务中,再执行调用Promise.then
Tasks宏任务:run script、 setTimeout callback
Microtasks微任务:Promise2 then
JS stack: Promise2 callback
Log: script start、script end、promise1、promise2
第三次执行
当微任务队列为空时,执行宏任务,执行setTmeout callback,打印日志
Tasks宏任务:setTimeout callback
Microtasks微任务:
JS stack: setTimeout callback
Log: script start、script end、promise1、promise2、setTimeout
第四次执行
清空Tasks队列和JS Stack
Tasks宏任务:setTimeout callback
Microtasks微任务:
JS stack:
Log: script start、script end、promise1、promise2、setTimeout
Node中的Event Loop是基于libuv实现的,而libuv是Node的新跨平台抽象层,libuv使用异步、事件驱动的编程方式,核心是提供i/o事件循环和异步回调。
一共分为6个阶段:
执行这两个回调需要设置一个毫秒数,由于system的调度可能会延时,达不到预期时间。
此阶段执行某些操作系统回调。
主要的两个功能:
执行I/O回调、处理轮询队列中的事件
当事件循环进入poll阶段并且在timers中没有可执行的定时器,将发生:
如果poll队列不为空,则事件循环将遍历其同步执行它们的callback队列,直到队列为空,或者达到system-dependent(系统相关限制)。
如果队列为空,将发生:
setImmediate()
回调需要执行,则会立即停止执行poll阶段并进入执行check阶段以执行回调setImmediate()
回调,poll阶段将等待callback被添加到队列中,然后立即执行此阶段运训人员在poll阶段完成后立即执行回调。
如果poll阶段闲置并且script已排队setImmediate()
,则事件循环到达check阶段执行而不是继续等待。
setImmediate()
是一个特殊的计时器,它在事件循环的一个单独阶运行,使用libuv API
来调度在poll阶段完成后执行的回调。
通常,当代码被执行时,事件循环最终将达到poll阶段,它将等待传入连接,请求等。
但是如果已经调度了回调setImmediate(),并且轮询阶段变为空闲,则它将结束并且达到check阶段,而不是等到poll事件。
根据被调用的时间以不同的方式表现:
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
执行定时器的顺序将根据调用它们的上下文而有所不同。如果从主模块中调用两者,那么时间将收到进程性能的限制。其结果也不相同。
Process.nextTick()
虽然是异步api的一部分,单不是事件循环的一部分。
Process.nextTick()
将callback
添加到next tick
队列。一旦当前事件轮询队列的任务全部完成,在next tick
队列中的所有callbacks
会被依次调用。
也就是说,如果存在nextTick队列,就会清空队列中的所有回调函数,并且优先于其他microtask执行。