Node.js 學習(一):《深入淺出Node.js》前四章筆記

1. Node 基礎

1.1. Node 的組件構成

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é)束。

經(jīng)典的異步調(diào)用

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ī)范

Node 規(guī)范

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

Node 模塊機制

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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容