若是对前端浏览器的时间循环不太清楚,请看这篇文章。那么node中的事件循环是什么样子呢?其实官方文档有很清楚的解释,本文先从node执行一个单文件提及,再讲事件循环。html
任何高级语言的存在都有必定的执行环境,好比浏览器的代码是在浏览器引擎中,那么在node环境中也有必定的执行环境。咱们先来看一下官网的依赖包有哪些?前端
上面就是nodejs中依赖的模块。那么这些模块之间是如何工做的呢?模块之间的工做关系以下图所示:node
主要过程以下:git
当即执行:能够理解为,须要v8引擎去处理的代码;
异步执行:并非真正的异步,能够理解为,不须要v8引擎处理的和须要异步处理的。github
一个线程有惟一的一个事件循环(event loop)。线程非安全。
这里须要理解两点:web
这可能和咱们理解的不太同样,Javascript代码是单线程的,可是libuv不是单线程的,他能够开启多个线程,libuv 提供了一个调度的线程池,线程池中的线程数目,默认是4个,最多1024个(为何?由于每个线程都会占用资源,而内存是有限的),关于线程池的能够看官方文档。浏览器
对数据的操做无非就是读和写,线程安全,简单来讲,就是一个线程对这一份数据具备独占性,只有当该线程操做完成,其余线程才能够进行操做,固然线程安全的概念远不止这些,详细能够看维基百科,这里就简单理解一下就好了。安全
事件循环图,以下所示:网络
主要分为下面几步:数据结构
step6: IO轮询执行,直到超时,在阻塞执行以前,会计算超时时间,也就是中止轮询的时间:
(用白话来理解,就是看有没有要关闭的,有的话,就直接往下走,没有的话,看看有哪一个事件比较急,到了点就去执行)
一般状况下来说,文件的I/O会调用线程池,可是网络请求的I/O老是用同一个线程。
node中全部的代码几乎都提供了同步(阻塞)和异步(非阻塞)的方式,你能够选择使用哪种方式,可是不要混合使用。
NodeJs中的定时器主要有三种:
三个定时器都有对应的取消函数:
setTimeout和setInterval行为和在浏览器环境中的行为相似,可是setTimeout和setImmediate有一点不一样。在libuv中能够看到,判断循环是否结束的时候,是须要判断是否还有待执行的函数,若是只剩下一个setTimeout或者setInterval函数,那么整个循环还会继续存在,node提供了一个函数,可让循环暂时休眠。
unref是可让setTimeout暂时休眠,ref能够再次唤醒
setImmediate是指定在事件循环结束执行的。主要发生在poll阶段以后
若是poll队列没空,则一直执行,直到对列空位置若是poll队列空了,有setImmediate事件,则会跳到check阶段
若是poll队列空了,没有setImmediate事件,就会查看哪个timer事件快要到期了,转到timers阶段
依据上面的解释,就有了setTimeout和setImmediate执行前后顺序的问题:
setTimeout(() => { console.log('timeout'); }) setImmediate(() => { console.log('immediate); });
先说答案:
可能会有两种状况: timeout immediate 或者 immediate timeout
为何?
主要是setTimeout在前或者后的问题,依赖于线程的执行速度。
主要是两个阶段:
二、v8引擎继续执行,走到setImmediate
因此根本缘由是在于事件循环执行了一次仍是两次。
那咱们接下来看看事件循环的逻辑
Node添加了这样一个API,这个并不在事件循环的机制内,可是和时间循环机制相关。先来看一下定义:
nextTick的定义是在事件循环的下一个阶段以前执行对应的回调。
虽然nextTick是这样定义的,可是它并非为了在事件循环的每一个阶段去执行的。
主要有下面两种应用场景:
let bar; function someAsyncApiCall(callback) { callback() process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1; // 输出 undefined 1
输出undefine的状况是,由于执行函数的时候,bar并无被赋值,而process.nextTick则能保证整个执行环境都准备好了再去执行
const server = net.createServer(); server.on('connection', (conn) => { }); server.listen(8080); server.on('listening', () => { });
当v8引擎执行完代码后,listen的回调会直接命中poll阶段,那么server的connect事件就不会执行
想要在构造函数中,去发送对应的事件,由于此时v8引擎尚未扫描到,而构造函数的代码会当即执行,就须要nextTick
const EventEmitter = require('events'); const util = require('util'); function MyEmitter() { EventEmitter.call(this); // 这样操做无效 this.emit('event'); // 应该这样 // process.nextTick(() => { this.emit('event'); }); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
上面三个案例,重点在于v8引擎是单线程当即执行,而libuv则是异步执行,想要在异步循环以前执行一些操做就须要process.nextTick