Node.js躬行記(1)——Buffer、流和EventEmitter
- 2020 年 5 月 25 日
- 筆記
- Node.js躬行記
一、Buffer
Buffer是一種Node的內置類型,不需要通過require()函數額外引入。它能讀取和寫入二進制數據,常用於解析網絡數據流、文件等。
1)創建
通過new關鍵字初始化Buffer對象的方式已經被廢棄,下面的代碼都已經過時。
new Buffer(array) new Buffer(arrayBuffer[, byteOffset[, length]]) new Buffer(buffer) new Buffer(size) new Buffer(string[, encoding])
目前有兩種方式創建Buffer對象,第一種是通過alloc()或allocUnsafe()兩個靜態方法,語法如下。
Buffer.alloc(size[, fill[, encoding]])
Buffer.allocUnsafe(size)
alloc()方法可接收三個參數,後兩個是可選的。第一個參數是長度,第二個參數是預填充的值,第三個參數是字符編碼,默認值是「utf8」。
const buf1 = Buffer.alloc(10); const buf2 = Buffer.alloc(10, "A"); const buf3 = Buffer.alloc(10, "A", "ascii");
如果打印buf3,那麼得到的將是一組十六進制數據,而非難以閱讀的二進制數據,如下所示。
<Buffer 41 41 41 41 41 41 41 41 41 41>
注意,Buffer的大小在創建時確定,後面也無法更改,其類型可由Buffer.isBuffer()辨別。當前Node支持的字符編碼有6種:
(1)ascii:僅適用於7位的ASCII數據。
(2)utf8:多位元組編碼的Unicode字符。許多網頁和其它文檔格式都在使用UTF-8。
(3)utf16le/ucs2:2或4個位元組,小端序編碼的Unicode字符。支持代理對(U+10000至U+10FFFF)。
(4)base64:Base64編碼,一種基於64個可打印字符來表示二進制數據的表示方法。
(5)latin1/binary:一種可編碼成單位元組字符串的方法。
(6)hex:將每個位元組編碼成兩個十六進制的字符。
allocUnsafe()是一個不安全的方法,因為它分配的內存片段是未初始化的,即沒有被清零。雖然這種設計性能優越,但分配的內存中可能會包含舊數據。
第二種是通過Buffer.from()方法創建Buffer對象,它的參數可以是數組、字符串、Buffer對象等,如下所示。
const buf = Buffer.from("A"); console.log(buf); //<Buffer 41>
2)轉換編碼
在讀取文件時,可通過toString()方法將Buffer對象轉換成字符串,如下所示,默認是UTF-8格式。
const fs = require('fs'); fs.readFile('./demo.txt', (err, buf) => { buf.toString(); //"你好,Node.js" buf.toString("base64"); //"5L2g5aW977yMTm9kZS5qcw==" });
二、流
Node中的stream模塊用於處理流式數據,許多內置的核心模塊都在其內部實現了流操作,流還適用於網絡傳輸、JSON解析器、RFC(遠程調用)等。流包括四個抽象類:
(1)Readable:可讀流,讀取底層的I/O數據源。
(2)Writeable:可寫流,將數據寫入到目標中。
(3)Duplex:雙工流,即可讀也可寫。
(4)Transform:轉換流,會修改數據的雙工流。
1)pipe()
在可讀流中,包含一個管道方法:pipe(),它的作用是關聯可讀流與可寫流,讓數據通過管道從可讀流進入到可寫流中。pipe()方法能接收一個Writable對象,並返回對目標流的引用,從而可形成鏈式調用。
在下面的示例中,會將origin.txt中的數據通過管道寫入到target.txt文件中,調用文件模塊的createReadStream()方法能得到一個Readable對象。
const fs = require('fs'); const readable = fs.createReadStream('./origin.txt'); const writable = fs.createWriteStream('./target.txt'); readable.pipe(writable);
2)事件
以可讀流為例,它的data事件可在接收到數據塊後觸發,而end事件會在流沒有數據時觸發。在下面的示例中,origin.txt文件包含的內容是「hello Node.js」。
const fs = require('fs'); const readable = fs.createReadStream('./origin.txt', {highWaterMark: 2}); readable.on("data", (chunk) => { console.log(`接收到 ${chunk.length} 個位元組的數據`, chunk.toString()); }); readable.on("end", () => { console.log("結束接收"); });
在調用createReadStream()方法時,包含一個highWaterMark屬性,其默認值為64KB,它的作用是限制可緩衝的位元組數。當定義為2後,每接收2個位元組的數據,就會觸發data事件,打印結果如下所示。
接收到 2 個位元組的數據 he 接收到 2 個位元組的數據 ll 接收到 2 個位元組的數據 o 接收到 2 個位元組的數據 No 接收到 2 個位元組的數據 de 接收到 2 個位元組的數據 .j 接收到 1 個位元組的數據 s 結束接收
可讀流還包含一個error事件,用於監聽異常,其事件處理程序會接收一個Error對象。在下面的示例中,會讀取不存在的文件,從而觸發error事件。
const readable = fs.createReadStream('./demo.txt'); readable.on("error", (err) => { console.log(err); //打印錯誤信息 });
3)實現流
當實現自定義的流時,需要繼承四個抽象類中的一個,表1列出了四個抽象類需要實現的方法。
抽象類 | 需要實現的方法 |
Readable | _read() |
Writeable | _write()、_writev()、_final() |
Duplex | _read()、_write()、_writev()、_final() |
Transform | _transform()、_flush()、_final() |
下面是一個自定義可寫流的例子,_write()方法中的encoding是一個字符串,表示字符編碼。
const { Writable } = require('stream'); class MyWritable extends Writable { constructor(options) { super(options); } _write(chunk, encoding, callback) { if (encoding === "buffer") { callback(); } } }
三、EventEmitter
Node的事件模塊目前只包含一個EventEmitter類(即事件觸發器),所有能觸發事件的對象都是EventEmitter類的實例。EventEmitter通常被用作基類,在Node內部,凡是提供事件機制的模塊都會繼承它。
在下面的示例中,聲明了一個EventEmitter實例,on()方法用於註冊監聽器,emit()方法用於觸發事件。在調用emit()方法時,傳遞了自定義的type參數。
const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('click', (type) => { console.log(`觸發${type}事件`); }); myEmitter.emit('click', "點擊");
注意,可註冊多個相同名稱的事件,監聽器會按照添加順序依次調用。事件模塊還提供了很多其它方法,例如off()用於解除事件綁定,once()可以只監聽一次事件。