你们都知道,javascript是一门单线程语言,所以为了实现主线程的不阻塞,Event Loop这样的方案应运而生。javascript
浏览器和node中Event loop并不同,浏览器的Event loop是在HTML5中定义的规范,而node中则由libuv库实现。java
全部同步任务都在主线程上执行,造成一个执行栈node
主线程以外,还存在一个任务队列。promise
任务队列分为macro-task(宏任务)和micro-task(微任务)。浏览器
macro-task(宏任务): setTimeout, setInterval, setImmediate, I/O等bash
micro-task(微任务): process.nextTick, 原生Promise(有些实现的promise将then方法放到了宏任务中),Object.observe(已废弃), MutationObserver等异步
整个最基本的Event Loop如图所示:socket
具体过程:oop
浏览器中,先执行当前栈,执行完主执行线程中的任务。ui
取出Microtask微任务队列中任务执行直到清空。
取出Macrotask宏任务中 一个 任务执行。
检查Microtask微任务中有没有任务,若是有任务执行直到清空。
重复3和4。
整个的这种运行机制又称为Event Loop(事件循环)
了解浏览器的Event loop后,查看下面例子,猜想浏览器是怎么输出的
console.log(1);
console.log(2);
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(function(){
console.log('Promise')
})
})
setTimeout(function(){
console.log('setTimeout2');
})
//浏览器输出:1 2 setTimeout1 Promise setTimeout2复制代码
在libuv内部有这样一个事件环机制。在node启动时会初始化事件环。
node中的event loop分为6个阶段,不一样于浏览器的是,这里每个阶段都对应一个事件队列,node会在当前阶段中的所有任务执行完,清空NextTick Queue,清空Microtask Queue,再执行下一阶段。
在node.js里,process 对象表明node.js应用程序,能够获取应用程序的用户,运行环境等各类信息。process.nextTick()方法将 callback 添加到next tick 队列
,而且nextTick优先级比promise等microtask高。
timers:执行setTimeout() 和 setInterval()中到期的callback。
I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
idle, prepare:队列的移动,仅内部使用
poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
check:执行setImmediate的callback
close callbacks:执行close事件的callback,例如socket.on("close",func)
查看下面例子加深对event loop的理解
在node执行下面代码,发现每次执行前后顺序不同,由于node须要启动时间,执行过程当中setTimeout可能到时间了也可能没到时间,因此这个前后顺序取决于node的执行时间。
setTimeout(function(){
console.log('timeout')
})
setImmediate(function(){
console.log('immediate')
})复制代码
i/o操做阶段完成后,会走check阶段,因此setImmediate会优先走
let fs=require('fs');
fs.readFile('./1.log',function(){
console.log('fs');
setTimeout(function(){
console.log('timeout')
})
setImmediate(funciton(){
console.log('setTimmediate')
})
})复制代码
nextTick应用场景
function Fn(){
this.arrs;
process.nextTick(()=>{ //根据nextTick的特性,能够先赋值,再在下一个队列中使用
this.arrs();
})
}
Fn.prototype.then=function(){
this.arrs=function(){console.log(1)}
}
let fn=new Fn();
fn.then();
//注意:nextTick千万不要写递归,否则会形成死循环。能够放一些比setTimeout优先执行的任务复制代码
同一个上下文下,MicroTask微任务会比MacroTask宏任务先运行。
浏览器是先取出一个MacroTask宏任务执行,再执行MicroTask微任务中的全部任务。Node是按照六个阶段执行,每一个阶段切换时,再执行MicroTask微任务队列
同个MicroTask队列下process.tick()
会优于Promise
setImmdieate()和setTimeout(),若是他们在异步i/o callback以外调用(在i/o内调用由于下一阶段为check阶段),其执行前后顺序是不肯定的,须要看loop的执行前的耗时状况。