Node.js核心入門(一)

Srtian發表於2018-04-01

前言:

因為以前學習Node.js並沒有真正意義上的去學習它,而是粗略的學習了npm的常用命令和Node.js一些模組化的語法,因此昨天花了一天的時間看了《Node.js開發指南》一書。通過這本書倒是讓我對Node.js的認識更為全面,但由於這本書出版時間過早,有些API已經發生了變化或已經被廢棄,而對於學習Node.js來說,核心部分又是最為重要的一環,因此我配合官方文件對這本書的第四章-Node.js核心進行了總結與梳理,由於水平有限,如有疏漏與錯誤,請指正。

正文

核心模組是Node.js的心臟,主要是有一些精簡高效的庫組成(這方面和Python有很大的相似之處),為Node.js提供了基礎的API。主要內容包括:

Node.js核心入門(一)

  • 全域性物件
  • 常用工具
  • 事件機制

Node.js核心入門(二)

  • 檔案系統訪問
  • HTTP伺服器與客戶端

全域性物件

全域性物件我想學過JavaScript的都知道在瀏覽器是window,在程式的任何地方都可以訪問到全域性物件,而在Node.js中,這個全域性物件換成了global,所有的全域性變數(除了global本身)都是global物件的屬性。而我們在Node.js中能夠直接訪問的物件通常都是global的屬性,如:console,process等。

全域性物件與全域性變數

global最根本的作用就是作為全域性變數的宿主。按照ECMAScript規範,滿足以下條件的變數即為全域性變數:

  • 在最外層定義的變數(在Node.js中不存在,因為Node.js的程式碼在模組中執行,不存在在最外層定義變數)
  • 全域性物件的屬性
  • 隱式定義的變數(即未定義而直接進行賦值的變數)

當我們定義一個全域性變數的時候,這個全域性變數會自動成為全域性變數的屬性。

process

process 物件是一個全域性變數,它提供當前 Node.js 程式的相關資訊,以及控制當前 Node.js 程式。通常我們在寫本地命令列程式的時候,少不了和它打交道。下面是它的最常用的成員方法:

1.process.argv

process.argv 屬性返回一個陣列,這個陣列包含了啟動Node.js程式時的命令列引數。第一個元素為process.execPath,第二個元素為當前執行的JavaScript檔案路徑,剩餘的元素為其他命令列引數。

例如儲存一個名為argv.js的檔案:

// print process.argv
process.argv.forEach((val, index) => {
  console.log(`${index}: ${val}`);
});

複製程式碼

則命令列執行時這樣的:

$ node process-args.js one two=three four

0: /usr/local/bin/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four
複製程式碼

2.process.stdout

process.stdout是標準輸出流,通常我們使用的console.log()輸出列印字元,而process.stdout.write()函式提供了更為底層的介面。

process.stdout.write('請輸入num1的值:');
複製程式碼

3.process.stdin

process.stdin是標準輸入流,初始時它是暫停的,要想從標準輸入讀取資料,我們必須恢復流,並手動編寫流的事件響應函式。

/*1:宣告變數*/
var num1, num2;
/*2:向螢幕輸出,提示資訊,要求輸入num1*/
process.stdout.write('請輸入num1的值:');
/*3:監聽使用者的輸入*/
process.stdin.on('data', function (chunk) {
    if (!num1) {
        num1 = Number(chunk);
        /*4:向螢幕輸出,提示資訊,要求輸入num2*/
        process.stdout.write('請輸入num2的值');
    } else {
        num2 = Number(chunk);
        process.stdout.write('結果是:' + (num1 + num2));
    }
});

複製程式碼

4.process.nextTick(callback[, ...args])

...args 呼叫 callback時傳遞給它的額外引數 process.nextTick()方法將 callback 新增到"next tick 佇列"。 一旦當前事件輪詢佇列的任務全部完成,在next tick佇列中的所有callbacks會被依次呼叫。 這種方式不是setTimeout(fn, 0)的別名。它更加有效率,因此別用setTimeout去代替process.nextTick。事件輪詢隨後的ticks 呼叫,會在任何I/O事件(包括定時器)之前執行。

console.log('start');
process.nextTick(() => {
  console.log('nextTick callback');
});
console.log('scheduled');

// start
// scheduled
// nextTick callback
複製程式碼

console

console 模組提供了一個簡單的除錯控制檯,類似於 Web 瀏覽器提供的 JavaScript 控制檯。該模組匯出了兩個特定的元件:

  • 一個 Console 類,包含 console.log() 、 console.error() 和 console.warn() 等方法,可以被用於寫入到任何 Node.js 流。
  • 一個全域性的 console 例項,可被用於寫入到 process.stdout 和 process.stderr。 全域性的 console 使用時無需呼叫 require('console')。(注意:全域性的 console 物件的方法既不總是同步的(如瀏覽器中類似的 API),也不總是非同步的(如其他 Node.js 流)。

比如全域性下的常見的console例項:

console.log('hello,world');
// hello,world
console.log('hello%s', 'world');
// helloworld
console.error(new Error('錯誤資訊'));
//  Error: 錯誤資訊
const name = '描述';
console.warn(`警告${name}`);
// 警告描述
console.trace() // 向標準錯誤流輸出當前的呼叫棧
複製程式碼

常見的Console類:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello,world');
// hello,world
myConsole.log('hello%s', 'world');
// helloworld
myConsole.error(new Error('錯誤資訊'));
// Error: 錯誤資訊
const name = '描述';
myConsole.warn(`警告${name}`);
//警告描述

複製程式碼

常用工具 util

util 模組主要用於支援 Node.js 內部 API 的需求。 大部分實用工具也可用於應用程式與模組開發者,用於彌補核心JavaScript的功能的不足。它的可以這樣呼叫:

const util = require('util');

複製程式碼

1.util.inspect(object[, options])

util.inspect() 方法返回 object 的字串表示,主要用於除錯和錯誤輸出。 附加的 options 可用於改變格式化字串的某些方面。它至少接受一個引數objet,即要轉換的引數,而option則是可選的,可選內容如下:

  • showHidden 如果為 true,則 object 的不可列舉的符號與屬性也會被包括在格式化後的結果中。 預設為 false。
  • depth 指定格式化 object 時遞迴的次數。 這對檢視大型複雜物件很有用。 預設為 2。 若要無限地遞迴則傳入 null。
  • colors 如果為 true,則輸出樣式使用 ANSI 顏色程式碼。 預設為 false,可自定義。
  • customInspect 如果為 false,則 object 上自定義的 inspect(depth, opts) 函式不會被呼叫。 預設為 true。
  • showProxy 如果為 true,則 Proxy 物件的物件和函式會展示它們的 target 和 handler 物件。 預設為 false。
  • maxArrayLength 指定格式化時陣列和 TypedArray 元素能包含的最大數量。 預設為 100。 設為 null 則顯式全部陣列元素。 設為 0 或負數則不顯式陣列元素。
  • breakLength 一個物件的鍵被拆分成多行的長度。 設為 Infinity 則格式化一個物件為單行。 預設為 60。

例如,檢視 util 物件的所有屬性:

const util = require('util');
console.log(util.inspect(util, { showHidden: true, depth: null }));

複製程式碼

2.util.callbackify(original)

util.callbackify(original)方法將 async 非同步函式(或者一個返回值為 Promise 的函式)轉換成遵循 Node.js 回撥風格的函式。 在回撥函式中, 第一個引數 err 為 Promise rejected 的原因 (如果 Promise 狀態為 resolved , err為 null ),第二個引數則是 Promise 狀態為 resolved 時的返回值。例如:

const util = require('util');

async function fn() {
  return await Promise.resolve('hello world');
}
const callbackFunction = util.callbackify(fn);

callbackFunction((err, ret) => {
  if (err) throw err;
  console.log(ret);
});
// hello world
複製程式碼

注意:

  • 回撥函式是非同步執行的, 並且有異常堆疊錯誤追蹤. 如果回撥函式丟擲一個異常, 程式會觸發一個 'uncaughtException' 異常, 如果沒有被捕獲, 程式將會退出。
  • null 在回撥函式中作為一個引數有其特殊的意義, 如果回撥函式的首個引數為 Promise rejected 的原因且帶有返回值, 且值可以轉換成布林值 false, 這個值會被封裝在 Error 物件裡, 可以通過屬性 reason 獲取。
function fn() {
  return Promise.reject(null);
}
const callbackFunction = util.callbackify(fn);

callbackFunction((err, ret) => {
    // 當Promise的rejecte是null時,它的Error與原始值都會被儲存在'reason'中。
  err && err.hasOwnProperty('reason') && err.reason === null;  // true
});
複製程式碼

事件驅動 events

events是Node.js最重要的模組,原因是Node.js本身架構就是事件式的,大多數 Node.js 核心 API 都採用慣用的非同步事件驅動架構,而它提供了唯一的介面,因此堪稱Node.js事件程式設計的及時。events 模組不僅用於使用者程式碼與 Node.js 下層事件迴圈的互動,還幾乎被所有的模組依賴。

所有能觸發事件的物件都是 EventEmitter 類的例項。 這些物件開放了一個 eventEmitter.on() 函式,允許將一個或多個函式繫結到會被物件觸發的命名事件上。 事件名稱通常是駝峰式的字串,但也可以使用任何有效的 JavaScript 屬性名。對於每個事件, EventEmitter支援 若干個事件監聽器。當事件發射時,註冊到這個事件的事件監聽器被依次呼叫,事件引數作 為回撥函式引數傳遞。

例如:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
// eventEmitter.on() 方法用於註冊監聽器
myEmitter.on('event', () => {
  console.log('觸發了一個事件!');
});
// eventEmitter.emit() 方法用於觸發事件
myEmitter.emit('event');

複製程式碼

下面讓我們來看看EventEmitter最常用的API:

  • EventEmitter.on(event, listener) 方法可以新增 listener 函式到名為 eventName 的事件的監聽器陣列的末尾。不會檢查 listener 是否已被新增。多次呼叫並傳入相同的 eventName 和 listener 會導致 listener 被新增與呼叫多次。
  • emitter.prependListener(eventName, listener)方法可以新增 listener 函式到名為 eventName 的事件的監聽器陣列的開頭。 不會檢查 listener 是否已被新增。 多次呼叫並傳入相同的 eventName 和 listener 會導致 listener 被新增與呼叫多次。
  • eventEmitter.emit() 方法允許將任意引數傳給監聽器函式。當一個普通的監聽器函式被 EventEmitter 呼叫時,標準的 this 關鍵詞會被設定指向監聽器所附加的 EventEmitter。
// 例項:
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// 列印:
//   b
//   a
複製程式碼
  • EventEmitter.once(event, listener) 為指定事件註冊一個單次監聽器,即監聽器最多隻會觸發一次,觸發後立刻解除該監聽器。
server.once('connection', (stream) => {
  console.log('首次呼叫!');
});
複製程式碼
  • EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽器, listener 必須是該事件已經註冊過的監聽器。(注意:removeListener 最多隻會從監聽器陣列裡移除一個監聽器例項。 如果任何單一的監聽器被多次新增到指定 eventName 的監聽器陣列中,則必須多次呼叫 removeListener 才能移除每個例項。)
const callback = (stream) => {
  console.log('有連線!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);

複製程式碼
  • EventEmitter.removeAllListeners([event]) 移除所有事件的所有監聽器,如果指定 event ,則移除指定事件的所有監聽器
const callback = (stream) => {
  console.log('有連線!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
複製程式碼

error 事件

EventEmitter 定義了一個特殊的事件 error ,它包含了“錯誤”的語義,我們在遇到異常的時候通常會發射 error 事件。當 error被髮射時,EventEmitter規定如果沒有響 應的監聽器,Node.js 會把它當作異常,退出程式並列印呼叫棧。我們一般要為會發射 error 事件的物件設定監聽器,避免遇到錯誤後整個程式崩潰。

var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error');
複製程式碼

繼承EventEmitter

大多數情況下,我們不會直接使用EventEmitter,而是在物件中繼承它,包括fs,http在內的只要是支援事件響應的核心模組都是EventEmitter的子類。這樣做的原因有以下兩個:

  • 具有某個實體功能的物件實現事件符合語義,事件的監聽和發射應該是一個物件的方法。
  • JavaScript 的物件機制是基於原型的,支援部分多重繼承,繼承 EventEmitter 不會打亂物件原有的繼承關係。

相關文章