標籤: node 原始碼 學習
原始碼: nianniuer
node背景,瞭解一下
(1)體系架構
Node.js主要分為四大部分,Node Standard Library,Node Bindings,V8,Libuv,架構圖如下:
- Node Standard Library 是我們每天都在用的標準庫,如Http, Buffer 模組。
- Node Bindings 是溝通JS 和 C++的橋樑,封裝V8和Libuv的細節,向上層提供基礎API服務。
- 這一層是支撐 Node.js 執行的關鍵,由 C/C++ 實現。
- V8 是Google開發的JavaScript引擎,提供JavaScript執行環境,可以說它就是 Node.js 的發動機。
- Libuv 是專門為Node.js開發的一個封裝庫,提供跨平臺的非同步I/O能力
- C-ares:提供了非同步處理 DNS 相關的能力。
- http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、資料壓縮等其他的能力。
程式碼結構
樹形結構檢視,使用 tree 命令
➜ nodejs git:(master) tree -L 1
.
├── AUTHORS
├── BSDmakefile
├── BUILDING.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── COLLABORATOR_GUIDE.md
├── CONTRIBUTING.md
├── GOVERNANCE.md
├── LICENSE
├── Makefile
├── README.md
├── ROADMAP.md
├── WORKING_GROUPS.md
├── android-configure
├── benchmark
├── common.gypi
├── config.gypi
├── config.mk
├── configure
├── deps
├── doc
├── icu_config.gypi
├── lib
├── node.gyp
├── out
├── src
├── test
├── tools
└── vcbuild.bat
複製程式碼
進一步檢視 deps目錄:
➜ nodejs git:(master) tree deps -L 1
deps
├── cares
├── gtest
├── http_parser
├── npm
├── openssl
├── uv
├── v8
└── zlib
複製程式碼
node.js核心依賴六個第三方模組。其中核心模組 http_parser, uv, v8這三個模組在後續章節我們會陸續展開。 gtest是C/C++單元測試框架。
(2)libuv
node.js最初開始於2009年,是一個可以讓Javascript程式碼離開瀏覽器的執行環境也可以執行的專案。 node.js使用了Google的V8解析引擎和Marc Lehmann的libev。Node.js將事件驅動的I/O模型與適合該模型的程式語言(Javascript)融合在了一起。隨著node.js的日益流行,node.js需要同時支援windows, 但是libev只能在Unix環境下執行。Windows 平臺上與kqueue(FreeBSD)或者(e)poll(Linux)等核心事件通知相應的機制是IOCP。libuv提供了一個跨平臺的抽象,由平臺決定使用libev或IOCP。在node-v0.9.0版本中,libuv移除了libev的內容。
libuv是一個高效能的,事件驅動的I/O庫,並且提供了跨平臺(如windows, linux)的API。 隨著libuv的日益成熟,它成為了擁有卓越效能的系統程式設計庫。除了node.js以外,包括Mozilla的Rust程式語言,和許多的語言都開始使用libuv。 libuv的官網:docs.libuv.org/en/v1.x/,英文…
(3)V8 concept[概念]
架構圖
現在 JS 引擎的執行過程大致是:原始碼 --->抽象語法樹 --->位元組碼 --->JIT--->原生程式碼。一段程式碼的抽象語法樹示例如下:
function demo(name) {
console.log(name);
}
複製程式碼
V8 更加直接的將抽象語法樹通過 JIT
技術轉換成原生程式碼,放棄了在位元組碼階段可以進行的一些效能優化,但保證了執行速度。 在 V8 生成原生程式碼後,也會通過 Profiler 採集一些資訊,來優化原生程式碼。雖然,少了生成位元組碼這一階段的效能優化, 但極大減少了轉換時間。
JIT
就是即時編譯器,可以根據位元組碼的使用頻率對常用的位元組碼生成本地機器指令(執行時),並且儲存下來
PS:
Tuborfan
將逐步取代Crankshaft
隨著Web相關技術的發展,JavaScript所要承擔的工作也越來越多,早就超越了“表單驗證”的範疇,這就更需要快速的解析和執行JavaScript指令碼。V8引擎就是為解決這一問題而生,在node中也是採用該引擎來解析JavaScript。
Node,開始學習!
1. Node能夠解決什麼問題?
應用場景:Node的首要目標是提供一種簡單的,用於建立高效能伺服器的開發工具 Web伺服器的瓶頸在於併發的使用者量,對比Java和Php的實現方式
Node在處理高併發 , I/O密集場景有明顯的效能優勢
- I/O (input/output 檔案的輸入輸出)
- 高併發,是指在同一時間併發訪問伺服器
- I/O密集指的是檔案操作、網路操作、資料庫,相對的有CPU密集,CPU密集指的是邏輯處理運算、壓縮、解壓、加密、解密
2. Node是什麼?
- Node.js 是一個基於
Chrome V8
引擎的 JavaScript 執行環境,讓JavaScript的執行效率與低端的C語言的相近的執行效率。 - Node.js 使用了一個
事件驅動
、非阻塞式 I/O
的模型,使其輕量又高效。 - Node.js 的包管理器
npm
,是全球最大的開源庫生態系統。
3. 程式與執行緒
程式是作業系統分配資源和排程任務的基本單位,執行緒是建立在程式上的一次程式執行單位,一個程式上可以有多個執行緒。
為什麼JavaScript是單執行緒?
- 這是由 Javascript 這門指令碼語言的用途決定的。
- Web Worker並沒有改變 JavaScript 單執行緒的本質。
// websocket
let worker = new Worker('./1.worker.js');
worker.postMessage(10000);
worker.onmessage = function (e) {
console.log(e.data);
}
console.log('main thread')
複製程式碼
3.1 談談瀏覽器
- 使用者介面-包括位址列、前進/後退按鈕、書籤選單等
- 瀏覽器引擎-在使用者介面和呈現引擎之間傳送指令(瀏覽器的主程式)
- 渲染引擎,也被稱為瀏覽器核心(瀏覽器渲染程式)
- 一個外掛對應一個程式(第三方外掛程式)
- GPU提高網頁瀏覽的體驗(GPU程式)
由此可見瀏覽器是多程式的,並且從我們的角度來看我們更加關心瀏覽器渲染引擎
3.2 渲染引擎
渲染引擎內部是多執行緒的,內部包含兩個最為重要的執行緒ui執行緒和js執行緒。這裡要特別注意ui執行緒和js執行緒是互斥的,因為JS執行結果會影響到ui執行緒的結果。ui更新會被儲存在佇列中等到js執行緒空閒時立即被執行.
3.3 js單執行緒
javascript在最初設計時設計成了單執行緒,為什麼不是多執行緒呢?如果多個執行緒同時操作DOM那豈不會很混亂?這裡所謂的單執行緒指的是主執行緒是單執行緒的(node其實也是多執行緒的),所以在Node中主執行緒依舊是單執行緒的。
- js執行緒和ui執行緒 是共享執行緒
- 不能兩個執行緒,操作同一個DOM
什麼是多執行緒?
- 同步
- 每次請求都會產生一個執行緒,可能會遇到同時操作一個檔案,會給檔案加鎖
- 可以開一個工作執行緒,但是歸主執行緒所管理
- 需要切換上下文執行
- 運用執行緒池可以優化多執行緒
什麼是單執行緒?
- 非同步
- 不會佔用過多記憶體,並且不需要在切換執行上下文
什麼是程式?
- 程式是系統分配任務和排程任務的基本單位
- 多程式 可能會遇到同時操作一個檔案會給檔案加鎖
3.4 其他執行緒
- 瀏覽器事件觸發執行緒(用來控制事件迴圈,存放setTimeout、瀏覽器事件、ajax的回撥函式)
- 定時觸發器執行緒(setTimeout定時器所線上程)
- 非同步HTTP請求執行緒(ajax請求執行緒)
單執行緒不需要管鎖的問題,這裡簡單說下鎖的概念。例如大家都要去上廁所,廁所就一個,相當於所有人都要訪問同一個資源。那麼先進去的就要上鎖。而對於node來說。就一個人去廁所,所以免除了鎖的問題!
3.5. 瀏覽器中的Event Loop
主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的,所以整個的這種執行機制又稱為Event Loop(事件迴圈)
- V8引擎解析JavaScript指令碼。
- 解析後的程式碼,呼叫Node API。
- libuv庫負責Node API的執行。它將不同的任務分配給不同的執行緒,形成一個Event Loop(事件迴圈),以非同步的方式將任務的執行結果返回給V8引擎。
- V8引擎再將結果返回給使用者。
queue stack
- queue佇列: 先進先出 push shift
- stack棧: 函式執行
3.6 Node.js的Event Loop
3.7. 同步與非同步
同步和非同步關注的是訊息通知機制
- 同步就是發出呼叫後,沒有得到結果之前,該呼叫不返回,一旦呼叫返回,就得到返回值了。 簡而言之就是呼叫者主動等待這個呼叫的結果。
- 而非同步則相反,呼叫者在發出呼叫後這個呼叫就直接返回了,所以沒有返回結果。換句話說當一個非同步過程呼叫發出後,呼叫者不會立刻得到結果,而是呼叫發出後,被呼叫者通過狀態、通知或回撥函式處理這個呼叫。
補充知識:巨集任務 && 微任務(vue $nextTick) 非同步方法
// 巨集任務 setTimeout
// 同步程式碼執行後才會執行非同步
// 根據時間排序,當時間到達後把對應的回撥放到佇列
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(4);
}, 1000);
}, 1000);
setTimeout(() => {
console.log(2);
}, 2000);
setTimeout(() => {
console.log(3);
}, 3000);
複製程式碼
// 巨集任務 setImmedate
// setImmedate只相容ie,預設是低於 setTimeout
setImmediate(function () {
console.log('setImmediate')
})
setTimeout(function () {
console.log('timeout')
}, 4);
console.log(1);
複製程式碼
// 巨集任務 MessageChannel
let messageChannel = new MessageChannel();
let prot2 = messageChannel.port2;
// postMessage是非同步執行的,要等待同步都執行完後才會被呼叫
messageChannel.port1.postMessage('111');
console.log(1);
prot2.onmessage = function (e) {
console.log(e.data);
}
console.log(2);
複製程式碼
// 巨集任務 MutationObserver廢棄了,相容/效能有問題
let observe = new MutationObserver(function () {
alert('已經dom更新好了')
});
observe.observe(app,{childList:true});
for(let i = 0;i<1000;i++){
app.appendChild(document.createElement('span'));
}
複製程式碼
3.8. 阻塞與非阻塞I/O
阻塞和非阻塞關注的是程式在等待呼叫結果(訊息,返回值)時的狀態.
- 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果之後才會返回。
- 非阻塞呼叫指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒。
3.9. 組合
同步非同步取決於被呼叫者,阻塞非阻塞取決於呼叫者
- 同步阻塞
- 非同步阻塞
- 同步非阻塞
- 非同步非阻塞
4. 什麼場合下應該考慮使用Node框架?
當應用程式需要處理大量併發的輸入輸出,而在向客戶端響應之前,應用程式並不需要進行非常複雜的處理。
- 聊天伺服器
- 電子商務網站