Node.js 事件循环
Node.js是单进程单线程应用程序,但是因为是V8引擎(from google,性能非常高)提供的异步执行回调接口,通过这些接口,可以处理大量的并发。
Node.js 几乎每一个API都支持回调函数。
Node.js 基本上所有的事件机制都是用设计模式中的观察者模式实现。
Node.js 单线程类似进去一个while(true)的事件循环,直到没有事件观察者退出,每一个异步事件都生成一个事件观察者,如果有事件发生,就调用该回调参数。
通俗的,就把每一个事件想象成一个一个先后启动windows后台程序(内存条很大,可以同时运行多个程序),比如浏览器,qq,微信,LOL,所不同的是,我们这里假象这些程序都会在运行不等时长后自动退出,并且在退出的瞬间,还会弹出提示。 那么做一下设想:
//依次启动 : 1.浏览器, 2.qq, 3.微信, 4.LOL
//运行时长: 浏览器(15s), qq(5s), 微信(9s), LOL(1s)
//退出提示: 1. 退出 LOL, 2. 退出qq, 3. 退出微信, 4. 退出浏览器,
以上过程,是异步的执行过程,我们可以把异步执行理解为多开后台,可以同时执行程序(内存条大),那么总共耗时15秒。 如果不是异步的话,那么就会产生阻塞,也就是会按照执行顺序一个个执行,上一个执行完毕,才执行下一个,相当于电脑的后台只能同一时间跑一个程序(内存小到爆炸的破电脑),那么阻塞执行的时长就会是:15s + 5s + 9s + 1s = 30s。
下图作为参考:
Node.js 回调函数
在Node.js中,由于JavaScript的语言特性,所以实现异步编程的直接体现就是回调。
但是,并不能说使用了回调之后程序就异步化了。回调函数在完成任务后就会被回调,Node使用了大量的回调函数,Node所有的API都支持回调函数。
回调有什么作用?有什么需求?或者说有什么好处呢?
通过回调函数,我们可以一边读取文件,一边执行其他的命令,在文件读取完成之后,我们将文件内容作为回调函数的参数返回。这样在执行代码的时候,就不会存在阻塞,或者等待文件I/O操作。这就大大提高了Node.js的性能。可以处理大量的并发请求。
一段经典的阻塞与非阻塞代码实例:
创建一个text文件,我们尝试用回调和不使用回调读取它。
阻塞:
js
var fs = require('fs');
var data = fs.readFileSync('hello.txt');
console.log(data.toString());
console.log('done');
bash
我是中国人
“I'm from China, I love my country!”
done
异步:
js
var fs = require('fs');
fs.readFile('hello.txt',function(err,data){
if(err) return console.log(err);
console.log(data.toString());
});
console.log('done');
bash
done
我是中国人
“I'm from China, I love my country!”
简单的说,因为读取文件,需要进行I/O操作,因此相对会花费更多的时间。 我们可以形象的理解为,通过异步操作,我们先开启文件读取操作,但是把它挂在后台执行,执行期间,由于直接打印'done'要消耗的时间小的多,所以整个程序将会先直接打印'done',然后程序会打印挂起执行的文件读取返回结果。
为了更直观的说明异步执行,我们将同样的逻辑代码,读取两个大小不同的文件:
var fs = require('fs');
//注意,下面两段代码,写在前面的文本文件占用空间更大。
fs.readFile('compareText2.txt',function(err,data){
if(err) return console.log(err);
console.log("-----这个文本更长一些-----");
console.log(data.toString());
console.log("-----这个文本更长一些-----");
console.log("done,
compareText2.txt");
});
fs.readFile('compareText1.txt',function(err,data){
if(err) return console.log(err);
console.log("#####这个文本更短一些#####");
console.log(data.toString());
console.log("#####这个文本更短一些#####");
console.log("done,
compareText1.txt");
});
bash
#####这个文本更短一些#####
-----LINE5-----
"我是中国人"
"我是中国人"
"我是中国人"
"我是中国人"
"我是中国人"
-----LINE5-----
#####这个文本更短一些#####
done,
compareText1.txt
-----这个文本更长一些-----
-----LINE150-----
"我是中国人"
"我是中国人"
"我是中国人"
//.......
//......共150行
//......
"我是中国人"
-----LINE150-----
-----这个文本更长一些-----
done,
compareText2.txt
我们发现读取大文件的逻辑写在前面,但是却后输出。 代码的执行过程是这样的:先触发执行读取大文件的函数,然后挂起,然后触发执行读取小文件的函数,由于小文件内容少,更快执行完,所以小文件读取函数的返回值返回的更快,于是小文件读取的返回结果更快被打印出来。