­

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()可以只監聽一次事件。