毕竟不是一个真正的教程,这里主要还是以普及和介绍为主,所以这一部分就是 Node.js 的其他部分介绍了,主要也就是事件触发、操作系统以及流的知识。
1 事件触发器
因为我们之前在浏览器中使用 JavaScript,所以知道 JS 通过事件处理了许多用户的交互:鼠标的单击、键盘按钮的按下、对鼠标移动的反应等等。
在后端,Node.js 也提供了使用 events 模块 构建类似系统的选项。
具体上,此模块提供了 EventEmitter 类,用于处理事件。
使用以下代码进行初始化:
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
该对象公开了 on 和 emit 方法:
- emit 用于触发事件
- on 用于添加回调函数(会在事件被触发时执行)
例如,创建 start 事件,并提供一个示例,通过记录到控制台进行交互:
eventEmitter.on('start', () => {
console.log('开始');
});
当运行以下代码时:
eventEmitter.emit('start');
事件处理函数会被触发,且获得控制台日志。
可以通过将参数作为额外参数传给 emit() 来将参数传给事件处理程序:
eventEmitter.on('start', number => {
console.log(`开始 ${number}`);
});
eventEmitter.emit('start', 23);
多个参数:
eventEmitter.on('start', (start, end) => {
console.log(`从 ${start} 到 ${end}`);
});
eventEmitter.emit('start', 1, 100);
EventEmitter 对象还公开了其他几个与事件进行交互的方法,例如:
- once():添加单次监听器
- removeListener() / off():从事件中移除事件监听器
- removeAllListeners():移除事件的所有监听器
下面详细的介绍一下事件模块:
2 事件模块
events 模块为提供了 EventEmitter 类,这是在 Node.js 中处理事件的关键,如上文简单的引入 events 即可使用,这里是另一个例子:
const EventEmitter = require('events');
const door = new EventEmitter();
事件监听器返回及使用以下事件:
- 当监听器被添加时返回 newListener
- 当监听器被移除时返回 removeListener
以下是最常用的方法的详细说明:
2.1 emitter.addListener()
emitter.on() 的别名,这是为了和 DOM API 保持一定的一致([DOM].addEventListener())。
2.2 emitter.emit()
触发事件, 按照事件被注册的顺序同步地调用每个事件监听器:
door.emit("slam"); // 触发 "slam" 事件。
2.3 emitter.eventNames()
返回字符串(表示在当前 EventEmitter 对象上注册的事件)数组:
door.eventNames();
2.4 emitter.getMaxListeners()
获取可以添加到 EventEmitter 对象的监听器的最大数量(默认为 10,但可以使用 setMaxListeners() 进行增加或减少)。
door.getMaxListeners();
2.5 emitter.listenerCount()
获取作为参数传入的事件监听器的计数:
door.listenerCount('open');
2.6 emitter.listeners()
获取作为参数传入的事件监听器的数组:
door.listeners('open');
2.7 emitter.off()
emitter.removeListener() 的别名,新增于 Node.js 10;这是为了和 emitter.on() 形成对应,简化记忆。
2.8 emitter.on()
添加当事件被触发时调用的回调函数,用法:
door.on('open', () => {
console.log('打开');
});
2.9 emitter.once()
添加当事件在注册之后首次被触发时调用的回调函数, 该回调只会被调用一次,不会再被调用:
const EventEmitter = require('events');
const ee = new EventEmitter();
ee.once('my-event', () => {
//只调用一次回调函数
});
2.10 emitter.prependListener()
当使用 on 或 addListener 添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用; 使用 prependListener 则可以在其他监听器之前添加并调用。
2.11 emitter.prependOnceListener()
当使用 once 添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用; 使用 prependOnceListener 则可以在其他监听器之前添加并调用。
2.12 emitter.removeAllListeners()
移除 EventEmitter 对象的所有监听特定事件的监听器:
door.removeAllListeners('open');
2.13 emitter.removeListener()
移除特定的监听器,可以通过将回调函数保存到变量中(当添加时),以便以后可以引用它:
const doSomething = () => {};
door.on('open', doSomething);
door.removeListener('open', doSomething);
2.14 emitter.setMaxListeners()
设置可以添加到 EventEmitter 对象的监听器的最大数量(默认为 10,但可以增加或减少):
door.setMaxListeners(50);
3 操作系统模块
为了能够真正的和操作系统交互,Node.js 同样提供了操作系统模块;该模块提供了许多函数,可用于从底层的操作系统和程序运行所在的计算机上检索信息并与其进行交互,引用方法保持一致:
const os = require('os');
有一些有用的属性可以告诉我们一些与处理文件有关的关键事项:
- os.EOL 可给出行定界符序列:在 Linux 和 macOS 上为 \n,在 Windows 上为 \r\n
- os.constants.signals 可告知所有与处理过程信号相关的常量,例如 SIGHUP、SIGKILL 等
- os.constants.errno 可设置用于错误报告的常量,例如 EADDRINUSE、EOVERFLOW 等
可以在 http://nodejs.cn/api/os.html#os_signal_constants 上阅读所有的内容。
现在看一下 os 提供的主要方法:
3.1 os.arch()
返回标识底层架构的字符串,例如 arm、x64、arm64:
3.2 os.cpus()
返回关于系统上可用的 CPU 的信息。
例如:
3.3 os.endianness()
根据是使用大端序或小端序编译 Node.js,返回 BE 或 LE:
3.4 os.freemem()
返回代表系统中可用内存的字节数:
3.5 os.homedir()
返回到当前用户的主目录的路径,例如:
3.6 os.hostname()
返回主机名:
3.7 os.loadavg()
返回操作系统对平均负载的计算,这仅在 Linux 和 macOS 上返回有意义的值;Windows 因为对系统负载的计算方法不同,返回值为 0。
例如:
3.8 os.networkInterfaces()
返回系统上可用的网络接口的详细信息,例如(因为是我真实的电脑,所以我把 mac 地址遮蔽了,见谅):
3.9 os.platform()
返回为 Node.js 编译的平台:
- darwin
- freebsd
- linux
- openbsd
- win32
- ...等
3.10 os.release()
返回标识操作系统版本号的字符串:
3.11 os.tmpdir()
返回指定的临时文件夹的路径:
3.12 os.totalmem()
返回表示系统中可用的总内存的字节数:
3.13 os.type()
标识操作系统:
- Linux
- macOS 上为 Darwin
- Windows 上为 Windows_NT(这是因为非 NT 架构的 Windows 系统现在占有率太低了,比如 Win 95、98、Me,所以默认如此显示)
3.14 os.uptime()
返回自上次重新启动以来计算机持续运行的秒数:
3.15 os.userInfo()
当前登录用户的信息:
4 流
流是为 Node.js 应用程序提供动力的基本概念之一,这是一种以高效的方式处理读/写文件、网络通信、或任何类型的端到端的信息交换。
流不是 Node.js 特有的概念,它是几十年前在 Unix 操作系统中引入的,程序可以通过管道运算符(|)对流进行相互交互。
例如,在传统的方式中,当告诉程序读取文件时,这会将文件从头到尾读入内存,然后进行处理。
使用流,则可以逐个片段地读取并处理(而无需全部保存在内存中)。
Node.js 的 stream 模块 提供了构建所有流 API 的基础,所有的流都是 EventEmitter 的实例(这样讲就容易明白了吧)。
4.1 为什么使用流
相对于使用其他的数据处理方法,流基本上提供了两个主要优点:
- 内存效率:无需加载大量的数据到内存中即可进行处理
- 时间效率:当获得数据之后即可立即开始处理数据,这样所需的时间更少,而不必等到整个数据有效负载可用才开始
4.2 流的示例
一个典型的例子是从磁盘读取文件。
使用 Node.js 的 fs 模块,可以读取文件,并在与 HTTP 服务器建立新连接时通过 HTTP 提供文件:
const http = require('http');
const fs = require('fs');
const server = http.createServer(function(req, res) {
fs.readFile(__dirname + '/data.txt', (err, data) => {
res.end(data);
});
});
server.listen(3000);
readFile() 读取文件的全部内容,并在完成时调用回调函数;而回调中的 res.end(data) 会返回文件的内容给 HTTP 客户端。
如果文件很大,则该操作会花费较多的时间,这个时候我们可以使用流,如下例:
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/data.txt');
stream.pipe(res);
})
server.listen(3000);
当要发送的数据块已获得时就立即开始将其流式传输到 HTTP 客户端,而不是等待直到文件被完全读取。
4.3 pipe()
上面的示例使用了 stream.pipe(res) 这行代码:在文件流上调用 pipe() 方法。
该代码的作用是获取来源流,并将其通过管道传输到目标流,在该示例中,文件流通过管道传输到 HTTP 响应。
pipe() 方法的返回值是目标流,它可以链接多个 pipe() 调用,从而非常方便,如下所示:
src.pipe(dest1).pipe(dest2);
此构造相对于:
src.pipe(dest1);
dest1.pipe(dest2);
要更容易理解和编写。
4.4 流驱动的 Node.js API
由于它的优点,许多 Node.js 核心模块提供了原生的流处理功能,最值得注意的有:
- process.stdin 返回连接到 stdin 的流
- process.stdout 返回连接到 stdout 的流
- process.stderr 返回连接到 stderr 的流
- fs.createReadStream() 创建文件的可读流
- fs.createWriteStream() 创建到文件的可写流
- net.connect() 启动基于流的连接
- http.request() 返回 http.ClientRequest 类的实例,该实例是可写流
- zlib.createGzip() 使用 gzip(压缩算法)将数据压缩到流中
- zlib.createGunzip() 解压缩 gzip 流
- zlib.createDeflate() 使用 deflate(压缩算法)将数据压缩到流中
- zlib.createInflate() 解压缩 deflate 流
4.5 不同类型的流
流分为四类:
- Readable:可以通过管道读取、但不能通过管道写入的流(可以接收数据,但不能向其发送数据);当推送数据到可读流中时,会对其进行缓冲,直到使用者开始读取数据为止
- Writable:可以通过管道写入、但不能通过管道读取的流(可以发送数据,但不能从中接收数据)
- Duplex:可以通过管道写入和读取的流,基本上相对于是可读流和可写流的组合
- Transform:类似于双工流、但其输出是其输入的转换的转换流
4.6 如何创建可读流
从 stream 模块获取可读流,对其进行初始化并实现 readable._read() 方法。
首先创建流对象:
const Stream = require('stream');
const readableStream = new Stream.Readable();
然后实现 _read:
readableStream._read = () => {};
也可以使用 read 选项实现 _read:
const readableStream = new Stream.Readable({
read() {}
});
现在,流已初始化,可以向其发送数据了:
readableStream.push('hi!');
readableStream.push('ho!');
4.7 如何创建可写流
若要创建可写流,需要继承基本的 Writable 对象,并实现其 _write() 方法。
首先创建流对象:
const Stream = require('stream');
const writableStream = new Stream.Writable();
然后实现 _write:
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString());
next();
};
现在,可以通过以下方式传输可读流:
process.stdin.pipe(writableStream);
4.8 如何从可读流中获取数据
使用可写流:
const Stream = require('stream');
const readableStream = new Stream.Readable({
read() {}
});
const writableStream = new Stream.Writable();
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString());
next();
};
readableStream.pipe(writableStream);
readableStream.push('hi!');
readableStream.push('ho!');
也可以使用 readable 事件直接获取可读流数据:
readableStream.on('readable', () => {
console.log(readableStream.read());
});
4.9 如何发送数据到可写流
使用流的 write() 方法:
writableStream.write('hey!\n');
4.10 使用信号通知已结束写入的可写流
使用 end() 方法:
const Stream = require('stream');
const readableStream = new Stream.Readable({
read() {}
});
const writableStream = new Stream.Writable();
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString());
next();
};
readableStream.pipe(writableStream);
readableStream.push('hi!');
readableStream.push('ho!');
writableStream.end();
眼看着基础的 Node.js 快要结束了,其实如果不算第三方的模块,它自带的东西的确就是这么多,我们的重点还是要有模块化开发的思路和方法论,或者大量的练习,毕竟熟能生巧么。
from 知乎专栏-全栈开发从零开始 https://ift.tt/2GIOit3
via IFTTT
评论
发表评论