1. 程式人生 > >Node.js躬行記(1)——Buffer、流和EventEmitter

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