1.背景
1.1 单线程
我们都知道JavaScript是单线程的,这是它语言特性决定的,它的主要用途 在于一些I/O操作,Dom操作等,单线程提高了效率,但同时有很多操作比如读取文件,Ajax获取数据等,都是一些比较耗时的操作,用户等不了太长时间,因此衍生出了事件循环。
1.2 任务队列
在JavaScript中,我们把队列分为两种,一种是同步任务,在主线程执行,只有一个任务执行完才可以执行下一个任务,一种是异步任务,它不进入主线程,而是通过另外一种方式,叫做任务队列
- 所有同步任务在Stack(栈)中执行
- 其他耗时操作在下面的Queue(队列)中执行,队列是先进先出的,一旦这些耗时操作执行完,就会放入队列中
- 一旦栈中任务执行结束,系统开始读取队列中排队的任务,每访问一个队列,会执行全部相同代码,比如setTimeout定时队列,再去下一个队列
- 整个过程不停循环,称为事件循环
2 Node.js
2.1 事件循环
Node.js中也有Event Loop,不过它的机制和和浏览器中的略微不同
- timers阶段:包括setTimeout,setInterval等
- pedding callbacks: 执行一些系统调用错误
- idle,prepare: node内部使用
- poll: 轮询阶段,检查I/O队列中定时器是否到时,例如(读取文件)
- check:执行setImmediate() 设定的callbacks
- close callbacks: 关闭回调
Event loop的每一次循环都需要依次经过上述的阶段。每个阶段都有自己的callback队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环。
其中还有一些微任务和宏任务的概念 微任务:promise.then process.nextTick(),其中nextTick()执行会比前者快 宏任务:setTimeout setImmidate(ie) messageChannel等
微任务总是会比宏任务先执行
2.2例子
setTimeout(() => {
console.log('timeout1');
process.nextTick(()=>{
console.log('nextTick1');
})
}, 1000);
setTimeout(()=>{
console.log('timeout2')
},1000)
复制代码
结果:timeout1 => timeout2 =>nextTick1 原因:首先都是外面宏任务,先执行时间队列,从上到下依次执行完timeout1=>timeout2,执行完后,到下一个队列,执行nextTick()
let fs = require('fs');
fs.readFile('./index.html',function(){
setImmediate(function(){
console.log('setImmediate')
});
setTimeout(function(){
console.log('setTimeout')
},0);
})
复制代码
结果:setImmediate => setTimeout 原因:读取文件是I/O操作,是在poll轮询阶段,读取完后,下一阶段是check,这时看有没有setImmediate,如果有,先执行这个,再执行setTimeout
let fs = require('fs');
setTimeout(function(){
Promise.resolve().then(()=>{
console.log('then2');
})
},0);
Promise.resolve().then(()=>{
console.log('then1');
});
fs.readFile('./index.html',function(){
process.nextTick(function(){
console.log('nextTick')
})
setImmediate(()=>{
console.log('setImmediate')
});
});
复制代码
结果:then1 => then2=> nextTick => setImmediate 原因:首先看最外面有微任务Promise.then,所以打印then1,接着到时间队列times,执行then2,times => poll 是不同阶段,每执行不同阶段, process.nextTick 不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调。所以 都要检查当前有没有promise,或者nextTick,如果有,先执行这些,有nextTick,再执行setImmediate