NodeJs基础二

6.Node.js EventEmitter-事件触发器
7.Node.js Buffer(缓冲区)
8.Node.js Stream(流)
9.Node.js模块系统

六、Node.js EventEmitter-事件触发器

Node.js所有的异步I/O操作在完成时都会发送一个事件到事件队列。
Node.js里面的许多对象都会分发事件:一个net.Server对象会在每次有新链接时分发一个事件,一个fs.readStream对象会在文件被打开的时候发出一个事件。所有这些产生事件的对象都是events.EventEmitter的实例。

1.EventEmitter类
events模块只提供一个对象events.EventEmitter。EventEmitter的核心就是事件触发与事件监听器功能的封装

1
2
3
4
// 引入events模块
var events = require('events');
// 实例化EventEmitter,创建emitter对象
var emitter = new events.EventEmitter();

实例

1
2
3
4
5
6
7
8
9
// 新建events.js文件
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event', function(){
console.log('some_event 事件触发');
});
setTimeout(function(){
event.emit('some_event');
}, 1000);

运行上述代码,1秒后控制台输出了‘some_event事件触发’。其原理是event对象注册了事件some_event的一个监听器,然后我们通过setTimeout在1000毫秒以后向event对象发送事件some_event,此时会调用some_event的监视器

EventEmitter的每个事件由一个事件名若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter支持若干个事件监听器。
当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递
实例

1
2
3
4
5
6
7
8
9
10
11
12
// event.js文件
var events = require('events');
var emitter = new events.EventEmitter();
// 注册到‘someEvent’事件的第一个监听器
emitter.on('someEvent', function(arg1, arg2){
console.log('listener1', arg1, arg2);
});
// 注册到‘someEvent’事件的第二个监听器
emitter.on('someEvent', function(arg1, arg2){
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'arg1参数', 'arg2参数');

执行结果

1
2
3
$ node event.js 
listener1 arg1参数 arg2参数
listener2 arg1参数 arg2参数

上述例子中,emitter为事件someEvent注册了两个事件监听器,然后触发了someEvent事件。
EventEmitter提供了多个属性,如on和emit。on函数用于绑定事件函数emit属性用于触发一个事件

2.方法
方法&描述
1.addListener(event,listener):为指定事件添加一个监听器到监听器数组尾部。
2.on(event,listener):为指定事件注册一个监听器,接受一个字符串event和一个回调函数。

1
2
3
server.on('connection', function(stream){
console.log('someone connected!');
});

3.once(event,listener):为指定事件注册一个单次监听器,触发后立刻解除该监听器。
4.removeListener(event, listener):移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。
5.removeAllListener([event]):移除所有事件的所有监听器。如果指定事件,则移除指定事件的所有监听器。
6.setMaxListeners(n):默认情况下EventEmitter添加的监听器超过10个就会输出警告信息。此函数用于提高监听器的默认限制的数量。
7.listeners(event):返回指定事件的监听数组。
8.emit(event,[arg1],[arg2],[…]):按参数的顺序执行每个监听器,付过事件有注册监听则返回true,否则返回false。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var events = require('events');
var eventEmitter = new events.EventEmitter();
//监听器#1
var listener1 = function listener1(){
console.log('监听器listener1执行');
}
// 监听器#2
var listener2 = function listener2(){
console.log('监听器listener2执行');
}
// 绑定connection事件,处理函数为listener1
eventEmitter.addListener('connection', listener1);
// 绑定connection事件,处理函数为listener2
eventEmitter.addListener('connection', listener2);

var eventListeners = require('events').EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + "个监听器连接事件");

// 触发connection事件
eventEmitter.emit('connection');

// 移除绑定的listener1函数
eventEmitter.removeListener('connection', listener1);
console.log('listener1不再受监听');

eventEmitter.emit('connection');

var eventListeners = require('events').EventEmitter.listenerCount(eventEmitter,'connection');
console.log(eventListeners + "个监听器连接事件");

console.log('程序执行完毕');

执行结果

1
2
3
4
5
6
7
8
$ node event.js
2个监听器连接事件
监听器listener1执行
监听器listener2执行
listener1不再受监听
监听器listener2执行
1个监听器连接事件
程序执行完毕

3.error事件
EventEmitter定义了一个特殊的事件error,它包含了错误的语义,我们在遇到异常的时候通常会触发error事件。
error被触发时,EventEmitter规定如果没有响应的监听器,Node.js会把它当作异常,退出程序并输出错误信息。
我们一般要为会触发error事件的对象设置监听器,避免遇到错误后整个程序崩溃。

1
2
3
var events = require('events');
var emitter = new event.EventEmitter();
emitter.emit('error');

4.继承EventEmitter
大多数时候我们不会直接使用EventEmitter,而是在对象中继承它。包括fs、net、http在内的,只要是支持事件响应的核心模块都是EventEmitter的子类。

  1. 首先,具有某个实体功能的的对象实现事件符合语义,事件的监听和触发应该是一个对象的方法。
  2. 其次,JavaScript的对象机制是基于原型的,支持部分多重继承,继承EventEmitter不会打乱对象原有的继承关系。

七、Node.js Buffer(缓冲区)

JavaScript语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流或文件流时,必须使用到二进制数据。因此在Node.js中,定义了一个Buffer类,该类用来创建一个专门存放二进制数据的缓存区
Buffer库为Node.js带来了一种存储原始数据的方法,可以让Node.js处理二进制数据,每当需要在Node.js中处理I/O操作中移动的数据时,就有可能使用Buffer库。原始数据存储在Buffer类的实例中。一个Buffer类似于一个整数数组,但它对应于V8堆内存之外的一块原始内存。

1.创建Buffer类

1
2
3
4
5
6
// 创建长度为10字节的Buffer实例
var buf = new Buffer(10);
// 通过给定的数组创建Buffer实例
var buf = new Buffer([10, 20, 30, 40, 50]);
// 通过一个字符串来创建Buffer实例
var buf = new Buffer('www.yizihan.github.io', 'utf-8');

2.写入缓冲区

1
buf.write(string[, fooset[, length]][, encoding])

string:写入缓冲区的字符串
offset:缓冲区开始写入的索引值,默认为0;
length:写入的字节数
encoding:使用的编码
返回值
返回实际写入的大小。
实例

1
2
3
buf = new Buffer(256);
len = buf.write('www.yizihan.github.io');
console.log('写入字节数' + len);

3.从缓冲区读取数据

1
buf.toString([encoding[, start[, end]])

encoing:使用的编码
start:指定开始读取的索引位置,默认为0
end:结束位置。
返回值
解码缓冲区数据并使用指定的编码返回字符串。
实例

1
2
3
4
5
6
buf = new Buffer(26);
for(var i=0; i<26; i++){
buf[i] = i + 97;
}
console.log(buf.toString('ascii')); // 输出: abcdefghijklmnopqrstuvwxyz
console.log(buf.toString('ascii', 0,5)); // 输出: abcde

4.将Buffer转换为JSON对象

1
buf.toJSON()

5.缓冲区合并

1
Buffer.concat(list[, totalLength])
1
2
3
4
var buffer1 = new Buffer('Node.js');
var buffer2 = new Buffer('yizihan.github.io');
var buffer3 = Buffer.concat([buffer1, buffer2]);
console.log('buffer3 内容' + buffer3.toString());

八、Node.js Stream(流)

Stream是一个抽象接口, Node中有很多对象是实现了这个接口。例如,对http服务器发起请求的request对象就是一个Stream,还有stdout(标准输出)。
Stream有四种流类型:
Readable:可读操作
Writable:可写操作
Duplex:可读可写操作
Transform:操作被写入数据,然后读出结果。
所有的Stream对象都是EventEmitter的实例,常用的事件有:
data:当有数据可读时触发
end:没有更多的数据可读时触发
error:在接收和写入过程中发生错误时触发
finish:所有数据已被写入到底层系统时触发

1.从流中读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 创建input.txt文件
菜鸟教程

// 创建main.js
var fs = require('fs');
var data = '';

// 创建可读流
var readerSteam = fs.creatReadStream('input.txt');

// 设置编码
readerStream.setEncoding('UTF8');

// 处理流事件 ==> data,end,and error
readerStream.on('data', function(chunk){
data += chunk; // 数据可读,将读取到的数据赋值给data
});
readerStream.on('end', function(){
console.log(data); // 所有数据读取完毕时,将读取到的数据输出
});
readerStream.on('error', function(){
console.log(err.stack); // 发生错误时
});
console.log('程序执行完毕');

// 执行结果
程序执行完毕
菜鸟教程

2.写入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建main.js文件
var fs = require('fs');
var data = '菜鸟教程';

// 创建一个可以写入的流,写入到文件output.txt中
var writerStream = fs.createWriteStream('output.txt');
// 使用utf8编码写入数据
writerStream.write(data, 'UTF8');
// 标记文件末尾
writerStream.end();
// 处理流事件 ==> finish and error
writerStream.on('finish', function(){
console.log('写入完成'); // 写入数据完成时
});
writerStream.on('error', function(err){
console.log(err.stack); // 发生错误时
});
console.log('程序执行完毕');

// 以上程序将data变量的数据写入到output.txt文件中,
$ node main.js
程序执行完毕
写入完成
// 查看output.txt文件的内容
菜鸟教程

3.管道流

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并传递到另外一个流中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建input.txt文件
菜鸟教程-管道流实例

// 创建main.js
var fs = require('fs');
// 创建一个可读流
var readerStream = fs.createReadStream('input.txt');
// 创建一个可写流
var writerSteam = fs.createWriterStream('output.txt');
// 管道读写操作,读取input.txt文件内容,并将内容写入到output.txt文件中
readerStream.pipe(writerStream);
console.log('程序执行完毕');

// 代码执行结果
程序执行完毕

// 查看output.txt文件的内容
菜鸟教程-管道流实例

4.链式流

链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。

1
2
3
4
5
6
7
8
// 创建compress.js文件
var fs = require('fs');
var zlib = require('zlib');
// 压缩input.txt文件为input.txt.gz
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
console.log('文件压缩完成');

执行以上操作后,我们可以看到当前目录下生成了input.txt的压缩文件input.txt.gz
接下来,解压该文件

1
2
3
4
5
6
7
var fs = require('fs');
var zlib = require('zlib');
// 解压input.txt.gz文件为input.txt
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('input.txt'));
console.log('文件解压完成');


九、Node.js模块系统

为了让Node.js的文件可以相互调用,Node.js提供了简单的模块系统
模块是Node.js应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或者编译过的C/C++扩展

1.创建模块

在Node.js中,创建一个模块非常简单,如下我们创建一个’main.js’文件

1
2
3
4
// 引入了hello.js文件
var hello = require('./hello');
// 调用了hello.js文件内的world()方法。
hello.world();

以上实例中,代码require('./hello')引入了当前目录下的hello.js文件(./为当前目录,node.js默认后缀为 js)。

Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。

1
2
3
4
// 创建hello.js文件
exports.world = function(){
console.log('Hello World');
}

在以上示例中,hello.js通过exports对象把world作为模块的访问接口,在main.js中通过require(‘./hello’)加载这个模块,然后就可以直接访问hello.js中的exports对象的成员函数(world())了

把一个对象封装到模块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = function(){
// ...
}

// hello.js
function Hello(){
var name;
this.setName = function(thyName){
name = thyName;
};
this.sayHello = function(){
console.log('Hello ' + name);
};
};
module.exports = Hello;

// main.js 这样就可以直接获得这个对象了
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoik');
hello.sayHello();

模块接口的唯一变化是使用 module.exports = Hello 代替了 exports.world = function(){} ;

2.服务端的模块放在哪里

Node.js中自带了一个叫做‘http’的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。
这把我们的本地变量变成了一个拥有所有http模块所提供的公共方法的对象。

从文件模块缓存中加载
尽管原生模块和文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。

从原生模块加载
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查是否在原生模块中列表中

从文件加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节参见Stream章节
require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块
  • ./mod或../mod,相对路径的文件模块
  • /pathtomodule/mod,绝对路径的文件模块
  • mod,非原生模块的文件模块