Node.js躬行記(1)——Buffer、流和EventEmitter
一、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()可以只監聽一次事件。
&n