前言
众所周知,JavaScript是单线程执行的,指的是一个进程里只有一个主线程。
JavaScript中的进程有:一个浏览器主进程、一个GPU进程、一个网络进程、多个渲染进程和插件进程。 JavaScript中的线程有:GUI渲染线程、JS引擎线程、计时器线程、异步HTTP请求线程、事件触发线程。
node事件循环
timer定时器阶段:执行如setTimeout和setInterval等的回调函数
I/O回调阶段:执行上一轮循环中未执行的I/O回调函数
Idle闲置阶段:仅供系统内部使用
Poll轮询阶段:这是一个至关重要的阶段,系统主要做两件事,一是回到timer阶段执行回调,二是执行I/O回调。会主动检测是否有新的I/O事件,若存在新的I/O事件,则执行其回调函数,适当的条件下,node将阻塞在这里。
check检查阶段:执行setImmediate的回调函数
close callbacks关闭回调阶段:执行socket.close()事件回调
浏览器事件循环
浏览器首先会执行主线程上的代码(相当于宏任务),遇到微任务便将其推入微任务队列中,遇到宏任务便推入宏任务队列中
当主线程中的代码执行完后,会检查微任务队列是否为空,若不为空,则将微任务队列中的微任务推至执行栈中执行。在执行的该微任务的过程中,如果又遇到宏任务则将其推入宏任务队列,遇到微任务则推入微任务队列
当微任务队列执行完为空时,检查宏任务队列是否为空,如不为空,则将对头宏任务推入执行栈开始执行。该过程中,若遇到宏任务则将其推入宏任务队列,若遇到微任务,则推入微任务队列
每当执行完一个宏任务,不管宏任务队列中是否还存在宏任务,都必须去检查微任务队列中是否有微任务,若存在微任务,则开始执行微任务
区别:
- 在浏览器事件循环中,每执行完一个宏任务,便要检查并执行微任务队列;而node事件循环中则是在“上一阶段”执行完,“下一阶段”开始前执行微任务队列中的任务。也就是说,node中的微任务是在两个阶段之间执行的。如果是node10及其之前版本:要看第一个定时器执行完,第二个定时器是否在完成队列中。
- 在浏览器事件循环中,process.nextTick()属于微任务,而且和其他微任务的优先级是一样的,不存在哪个微任务的优先级高就先执行谁。但是在node中,process.nextTick()的优先级要高于其他微任务,也就是说,在两个阶段之间执行微任务时,若存在process.nextTick(),则先执行它,然后再执行其他微任务。
例子:
console.log('script开始'); setTimeout(() => { console.log('宏任务1'); Promise.resolve().then(function () { console.log('微任务2') }) },0); setTimeout(() => { console.log('宏任务2'); Promise.resolve().then(function() { console.log('微任务3') }) }) Promise.resolve().then(function () { console.log('微任务1'); }) console.log('script结束');
浏览器端运行结果
script开始 script结束 微任务1 宏任务1 微任务2 宏任务2 微任务3
node端运行结果
script结束 微任务1 宏任务1 微任务2 宏任务2 微任务3
小结
- 浏览器是一个宏任务+一个微任务队列
- node是一个宏任务队列+一个微任务队列