1. Node 基礎
1.1. Node 的組件構成

libuv/libuv : Cross-platform asynchronous I/O .
Node 的結(jié)構與 Chrome 相似,都是基于事件驅(qū)動的異步架構。 Node 通過事件驅(qū)動來服務 I/O 。
1.2. Node 的特點
1.2.1. 異步 I/O
在 Node 中,我們可以從語言層面很自然的進行并行 I/O 操作, 每個調(diào)用之間無須等待之前的 I/O 調(diào)用結(jié)束。

1.2.2. 事件與回調(diào)函數(shù)
Node 配合異步 I/O , 將事件點暴露給業(yè)務邏輯。 事件的編程方式具有輕量級、松耦合、只關注事務點等優(yōu)勢, Node 利用回調(diào)函數(shù)接受異步調(diào)用返回的數(shù)據(jù)。 但是代碼的編寫順序和執(zhí)行順序并無關系,因此在流程控制方面需要劃分業(yè)務和提煉事件。
1.2.3. 單線程
單線程自身的弱點有:
- 無法充分利用硬件資源
- 大量計算占用 CPU 導致無法繼續(xù)調(diào)用異步 I/O
- 無錯誤處理時,會引起整個應用退出
Node 使用 child_process 模塊, 將計算分發(fā)到個個子進程,再通過進程之間的事件消息來傳遞結(jié)果。
1.2.4. 跨平臺

1.3. Node 的應用場景
1.3.1. I/O 密集型 或者 DIRT (data-intensive real-time)
I/O 密集的優(yōu)勢主要在于 Node 利用事件循環(huán)的處理能力。
1.3.2. CPU 密集型
比較計算運行時間和 I/O 的耗時,適當調(diào)整和分解大型運算任務為多個小任務,不阻塞 I/O 調(diào)用。
1.3.3. 分布式應用
Node 高效利用并行 I/O 可以來查詢分布式數(shù)據(jù)庫。
2. 模塊機制
2.1. Node 規(guī)范

2.2. Node 的模塊實現(xiàn)

2.3. 包與 NPM
2.3.1. 包結(jié)構
符合 CommonJS 規(guī)范的包目錄:
- package.json: 包描述文件
- bin: 存放可執(zhí)行二進制文件
- lib: 存放 JavaScript 代碼的目錄
- doc: 存放文檔
- test: 存放單元測試用例的代碼
2.3.2. 包描述文件與 NPM
包描述文件字段:
- name: 包名
- description: 包簡介
- version: 版本號
- keywords: 關鍵詞數(shù)組
- maintainers: 包維護者
- contributors: 貢獻者
- bugs: 提交 bug 地址
- licenses: 許可證
- repositories: 托管源代碼位置
- dependencies: 當前包所需要依賴的包
- homepage: 包的網(wǎng)站
- os: 操作系統(tǒng)
- cpu: CPU 支持
- engine: JavaScript 引擎
- builtin: 是否是內(nèi)建在底層系統(tǒng)的標準組件
- directories: 包目錄說明
- implements: 實現(xiàn)規(guī)范
- scripts: 腳本
NPM 字段:
- author: 包作者
- bin: 命令行工具
- main: 模塊引入入口
- devDependencies: 開發(fā)時需要依賴的包
3. 異步 I/O
Node 的基調(diào)是異步 I/O 、事件驅(qū)動和單線程。
3.1. 為什么使用異步 I/O
Node 利用單線程,遠離多線程死鎖、狀態(tài)同步等問題;利用異步 I/O ,讓單線程遠離阻塞,以更好地使用 CPU 。
3.1.3. Node 的異步 I/O 實現(xiàn)
事件循環(huán)、觀察者、請求對象和 I/O 線程池共同構成了 Node 異步 I/O 模型。

Node 中的觀察者:

4. 異步編程
4.1. 函數(shù)式編程
4.1.4. 高階函數(shù)
高階函數(shù)遵循一個明確的定義:
- 它是一等公民。
- 以一個函數(shù)作為參數(shù)。
- 以一個函數(shù)作為返回結(jié)果。
高階函數(shù)形成了一種后續(xù)傳遞風格的程序編寫,將函數(shù)的業(yè)務重點從返回值轉(zhuǎn)移到了回調(diào)函數(shù)中。
4.1.5. 偏函數(shù)與 Curry 化
使用 Curry 化有利于創(chuàng)建更流暢的接口,同時使得代碼閱讀起來越來越像它行為的描述。
4.2. 異步編程的優(yōu)勢與難點
4.2.1. 優(yōu)勢
Node 帶來的最大特性莫過于基于事件驅(qū)動的非阻塞 I/O 模型。 Node 為了解決編程模型中阻塞 I/O 的性能問題,采用了單線程模型, 對于 CPU 密集型程序建議將大量的計算分解為諸多小量計算,對 CPU 的耗用不要超過 10 ms, 可以使用 setImmediate() 調(diào)度。
4.2.2. 難點
- 異常處理
- 函數(shù)嵌套過深
- 阻塞代碼
- 多線程編程
4.3. 異步編程解決方案
4.3.1. 事件發(fā)布/訂閱模式
事件發(fā)布/訂閱模式可以實現(xiàn)一個事件與多個回調(diào)函數(shù)的關聯(lián),這些回調(diào)函數(shù)又稱為事件偵聽器。偵聽器可以很靈活的添加和刪除,使得事件和具體處理邏輯之間可以很輕松的關聯(lián)和解耦。
事件發(fā)布/訂閱模式自身并無同步和異步調(diào)用問題,但在 Node 中, emit() 調(diào)用多半是伴隨事件循環(huán)而異步觸發(fā)的,所以我們說事件發(fā)布/訂閱廣泛應用于異步編程。
Node 對事件發(fā)布/訂閱的機制做了一些額外的處理:
- 如果對一個事件添加了超過 10 個偵聽器,將會得到一條警告。
- 如果運行期間的錯誤觸發(fā)了 error 事件, EventEmitter 會檢查是否有對 error 事件添加過偵聽器。如果添加了,這個錯誤交由該偵聽器處理,否則這個錯誤將會作為異常拋出,如果外部沒有捕獲這個異常,將會引起線程退出。
當一個事件被觸發(fā),所有和它相關的偵聽器將被同步調(diào)用,偵聽器中返回的數(shù)據(jù)會被忽視和丟棄。此外偵聽器中的 this 被特意的指向與它相關的事件對象,所以需要特別留意使用箭頭函數(shù)的情景。
如果需要異步調(diào)用偵聽器,可以使用 setImmediate() 或者 process.nextTick():
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
setImmediate(() => {
console.log('this happens asynchronously');
});
});
myEmitter.emit('event', 'a', 'b');
4.3.2. Promise/Deferred 模式
事件方式的缺點是即使是分支流程,也需要為每一個分支設置事件發(fā)布/訂閱,并且偵聽器是同步執(zhí)行。那么是否有一種先執(zhí)行異步調(diào)用,延遲傳遞處理的方式呢?
4.3.2.1. Promises/A 規(guī)范
Promises/A 提議對單個異步操作做出如下抽象定義:
- Promise 操作只會處在 3 種狀態(tài)的一種:未完成態(tài)、完成態(tài)和失敗態(tài)。
- Promise 的狀態(tài)只會出現(xiàn)從未完成態(tài)向完成態(tài)或失敗態(tài)轉(zhuǎn)化,不能逆反。完成態(tài)和失敗態(tài)不能互相轉(zhuǎn)化。
- Pormise 的狀態(tài)一旦轉(zhuǎn)化,將不能被更改。
一個 Promise 對象只要具備 then() 方法即可。
- 接受完成態(tài)、錯誤態(tài)的回調(diào)方法。
- 可選地支持 progress 事件回調(diào)作為第三個方法。
- then() 方法只接受 function 對象。
- then() 方法繼續(xù)返回 Promise 對象,以實現(xiàn)鏈式調(diào)用。
從事件發(fā)布/訂閱的角度出發(fā),可粗略的認為 then() 方法是將回調(diào)函數(shù)(偵聽器)存放起來。那么觸發(fā)事件的任務是由 Deferred 對象實現(xiàn)。但是 Deferred 對象會保持狀態(tài)不變,再對 Promise 對象添加回調(diào)函數(shù),也會立即得到結(jié)果,而事件的特點是,你錯過了它,再去監(jiān)聽是得不到結(jié)果的。

4.3.2.2. 事件發(fā)布/訂閱模式和 Promises/Deferred 模式的區(qū)別
// Promises 和 事件發(fā)布/訂閱的區(qū)別
const EventsEmitter = require('events');
const emitter = new EventsEmitter();
const promise = new Promise(function(resolve, reject) {
console.log("promise");
resolve();
});
emitter.emit("done", "emit_ignore") // 觸發(fā)事件,但是沒有事件被訂閱
emitter.on("done", (data) => {
console.log(data + " done");
promise.then(function() { // 進入微指令隊列(mirco),延遲執(zhí)行。
console.log("then in done");
});
});
emitter.emit("done", "emit") // 觸發(fā)事件,事件被訂閱,事件偵聽器立即執(zhí)行(同步)
console.log("This is script.")
promise.then(function() { // 進入微指令隊列(mirco),延遲執(zhí)行。
console.log("then");
});
上面程序的結(jié)果如下:
promise
emit done
This is script.
then in done
then
4.3.3. 流程控制庫
- 尾觸發(fā)與 Next
- Async