Q: 好了,我们继续谈谈 Node.js 吧,我记得上次说到了「非阻塞」和「事件驱动」,这引起了我的好奇心,但是又给我泼了一桶冷水,这两个词有点高端。A:别急,我们先来看一个简单的场景:人人网刷朋友新鲜事你应该用过吧?实现这样的功能有一种简单的方式,是让用户与服务器之间保持一个长轮询。但是它与普通的 Ajax 不一样,服务器不会立马返回信息,它会先 hold 住,等待应该返回信息了,它才会返回信息(比如你的好友发了一条新的状态)。从传统服务端来看(比如 Apache),每次一个新用户连到你的网站上,你的服务器得新开一个连接,每个连接都需要占用一个线程,这些线程大部分都是闲着的(比如等你的好友发状态,查数据库等),虽然它们是闲着的,但是照样占用了内存,也就是说,如果用户达到一定的规模,服务器的内存就会耗光而瘫痪。解决办法有很多,比如说使用线程池,但是它依然是阻塞的,如果线程池里的所有线程都被阻塞(网速慢,被人恶意暂用)那么接下来的请求将会排队等待。Node.js 就不相同了,它使用了「非阻塞」与「事件驱动」模型,你可以把它想象成一个 Event Loop 循环,这个循环会一直跑。一个新的请求来了,Event Loop 接收这个请求,然后交给其他线程,比如查询数据库,然后响应一个 callback,接着接收其他请求,而不是等待数据库结果的返回。如果数据库返回了结果,服务端将会把它返回给客户端,并继续循环。这就是事件驱动:服务端只在有事情发生时,才会有相应的处理(或者是接受请求,或者是一些 callback)。Q:这么看来,Node.js 的非阻塞和事件驱动,是基于这个 Event Loop 的?A:是的,简单来讲,Node.js 的 Event Loop 是基于 libuv,而浏览器的 Event Loop 则是在 html5 规范 中定义,具体实现交给浏览器厂商。Q:有趣了,还有两种 Event Loop。A:对比来看,它们有点相似:在浏览器中比较简单,值得注意的一点是,会在每个 tasks 之后,会把当前 microtask 队列里的任务都执行完:Node.js 稍微复杂一点,每次 Event Loop 都需要经过六个阶段,每一个阶段之后,都会执行 nextTick、microtasks (resolved promise, 等): ┌───────────────────────┐┌─>│ timers │ <─── setTimeout/setInterval callback│ └──────────┬────────────┘ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <─────────────── │ ││ │ │ microTask queue ││ ┌──────────┴────────────┐ └─────────────────────────┘│ │ I/O callbacks ││ └──────────┬────────────┘ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <─────────────── │ ││ │ │ microTask queue ││ ┌──────────┴────────────┐ └─────────────────────────┘│ │ idle, prepare │ <─── 仅内部使用│ └──────────┬────────────┘│ │ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <──────────────── │ ││ │ │ microTask queue ││ │ └─────────────────────────┘│ │ ┌─────────────────────────┐│ ┌──────────┴────────────┐ │ incoming: ││ │ poll │ <────┤ connections, ││ └──────────┬────────────┘ │ data, etc ││ │ └─────────────────────────┘│ │ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <──────────────── │ ││ │ │ microTask queue ││ │ └─────────────────────────┘│ ┌──────────┴────────────┐│ │ check │ <─── setImmediate callback│ └──────────┬────────────┘ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <─────────────── │ ││ │ │ microTask queue ││ ┌──────────┴────────────┐ └─────────────────────────┘│ │ close callbacks │ <─── eg: socket.on(“close”,func)│ └──────────┬────────────┘ ┌─────────────────────────┐│ │ │ nextTick queue ││ │ <─────────────── │ │└─────────────┴ │ microTask queue │ └─────────────────────────┘来一段简单的代码,猜猜浏览器(Chrome)和 Node.js 分别输出什么:console.log(‘start’);setTimeout(() => { console.log(’timer1’); Promise.resolve().then(() => { console.log(‘promise1’); });}, 0);setTimeout(() => { console.log(’timer2’); Promise.resolve().then(() => { console.log(‘promise2’); });}, 0);console.log(’end’);Q:浏览器 (Chrome) 中肯定是输出 start、end、time1、promise1、time2、promise2,至于 Node.js 中,我猜也是一样的吧?A:我们先来验证一下:浏览器中:startendtimer1promise1timer2promise2Node.js 中:startendtimer1timer2promise1promise2看来和想象中的不一样,别急,看个动图就会明白了:浏览器中:Node.js 中:Q:原来是这样,那如果在每个 setTimeout callback 里加上 process.nextTick 那么是比 Promise.then 先执行?A:是的,还记得上面所说过的吗,在每个阶段后都会执行 nextTick queue 以及 micktasks queue,nextTick queue 的优先级比 micktasks queue 高。Q:我懂了。对了,我记得你提到了 libuv,它是什么?不是说 Node.js 使用的是 v8 吗,它和 v8 又有什么关系?A:…未完待续…其他问答方式学 Node.js(一)参考https://blog.csdn.net/wtopps/…Node.js 是用来做什么的? - 厂长的回答Promises, Next-Ticks and Immediates— NodeJS Event Loop Part 3更快的异步函数和 Promise