深入學(xué)習(xí)Nodejs(1)——基礎(chǔ)概念

前言

現(xiàn)在 Node.js 對于前端越來越重要雨席,筆者現(xiàn)在也感受到了學(xué)好 Node.js 可以極大的提升前端競爭力。之前也或多或少的接觸過础拨,但是沒有一個系統(tǒng)的學(xué)習(xí)過程骄崩,所以筆者接下來打算系統(tǒng)的去學(xué)習(xí) Node.js,同時希望建立一個從零到一學(xué)習(xí) Node 的一個博客葛峻。

希望我的學(xué)習(xí)經(jīng)驗可以幫助到大家锹雏。

Node.js 是什么

都 2020 年了,大家對 Node 肯定都有了初步的了解术奖,但是我還是想先介紹一下什么是 Node

Node.js是一個基于 Chrome V8 引擎的JavaScript運行環(huán)境(runtime)礁遵。

Node不是一門語言是讓js運行在后端的運行時,并且不包括javascript全集,因為在服務(wù)端中不包含DOM和BOM。Node也提供了一些新的模塊例如http,fs模塊等采记。

Node.js 使用了事件驅(qū)動榛丢、非阻塞式 I/O 的模型,使其輕量又高效挺庞。并且 Node.js 的包管理器 npm晰赞,是全球最大的開源庫生態(tài)系統(tǒng)。

為什么使用要學(xué)習(xí) Node.js

(1)首先 Node 在處理高并發(fā)选侨,IO密集場景有明顯優(yōu)勢掖鱼。

高并發(fā)是指同一時間并發(fā)訪問服務(wù)器

IO即輸入輸出,是指文件讀寫援制,網(wǎng)絡(luò)操作戏挡,數(shù)據(jù)庫操作等。相對的是 CPU 密集型晨仑,意思是邏輯處理運輸頻繁褐墅,一般指解密拆檬、加密、壓縮妥凳、解壓等操作竟贯。

(2)其次,現(xiàn)在前端開發(fā)越來越離不開 Node逝钥。日常工作中的 webpack屑那、cli 腳手架工具等都是由 Node 編寫。前端學(xué)會 Node 會極大的增加自己的話語權(quán)艘款,提高自己的競爭力持际。

異步非阻塞 IO

傳統(tǒng)語言例如大部分都是采用同步阻塞的方式,即當(dāng)我讀取某個文件的內(nèi)容時哗咆,我必須等待所有文件內(nèi)容讀取完畢才能進行下面的內(nèi)容蜘欲,因此整個線程都將停止執(zhí)行,造成了阻塞晌柬,極大程度的降低了執(zhí)行效率姥份。

由于 Node.js 采用異步非阻塞的模型,因此當(dāng)我對讀取某個文件時空繁,將立即執(zhí)行下面的代碼,當(dāng)文件讀取完畢朱庆,將處理結(jié)果放入我們的回調(diào)函數(shù)當(dāng)中盛泡,增加了執(zhí)行效率。

阻塞模式下娱颊,一個線程只能處理一個 IO傲诵,如果想并發(fā)處理多個任務(wù)只能開啟多個線程。而非阻塞模式下一個線程就可以處理多個 IO箱硕,CPU 的利用率可以達到最大拴竹。

單線程

Node.js 采用單線程模式,這也良好的適用了單線程非阻塞的模式剧罩。當(dāng) Node.js 需要處理 IO 時栓拜,會將任務(wù)交給內(nèi)部的 libuv,libuv 處理完成后惠昔,通過 IO 事件驅(qū)動機制幕与,將結(jié)果返回給 Node.js 的線程。

事件驅(qū)動

每一次 IO 操作完成都會觸發(fā)相應(yīng)的事件镇防,由于 Node.js 是單線程啦鸣,所以同一時刻只能處理一個任務(wù),那多余的任務(wù)只能放到隊列中等待被調(diào)用来氧。而為了處理這一過程 Node.js 采用了事件循環(huán)的策略诫给,類似于瀏覽器的事件循環(huán)香拉,但有很多出入。

事件循環(huán)

當(dāng) Node.js 啟動后中狂,它會初始化事件循環(huán)凫碌,處理已提供的輸入腳本,它可能會調(diào)用一些異步的 API吃型、調(diào)度定時器证鸥,或者調(diào)用 process.nextTick(),然后開始處理事件循環(huán)勤晚。

下面是一張來自官網(wǎng)的事件循環(huán)的描述圖枉层。

image.png

事件循環(huán)官網(wǎng)介紹的非常清楚,我這里把官網(wǎng)的內(nèi)容搬運過來赐写。

注意:每個框被稱為事件循環(huán)機制的一個階段鸟蜡。

每個階段都有一個 FIFO 隊列來執(zhí)行回調(diào)。雖然每個階段都是特殊的挺邀,但通常情況下揉忘,當(dāng)事件循環(huán)進入給定的階段時,它將執(zhí)行特定于該階段的任何操作端铛,然后執(zhí)行該階段隊列中的回調(diào)泣矛,直到隊列用盡或最大回調(diào)數(shù)已執(zhí)行。當(dāng)該隊列已用盡或達到回調(diào)限制禾蚕,事件循環(huán)將移動到下一階段您朽,等等。

由于這些操作中的任何一個都可能調(diào)度 更多的 操作和由內(nèi)核排列在輪詢階段被處理的新事件换淆, 且在處理輪詢中的事件時哗总,輪詢事件可以排隊。因此倍试,長時間運行的回調(diào)可以允許輪詢階段運行長于計時器的閾值時間讯屈。

階段概述

  • 定時器:本階段執(zhí)行已經(jīng)被 setTimeout() 和 setInterval() 的調(diào)度回調(diào)函數(shù)。
  • 待定回調(diào)(pending callbacks):執(zhí)行延遲到下一個循環(huán)迭代的 I/O 回調(diào)县习。
  • idle, prepare:僅系統(tǒng)內(nèi)部使用涮母。
  • 輪詢(poll):檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù)躁愿,那些由計時器和 setImmediate() 調(diào)度的之外)哈蝇,其余情況 node 將在適當(dāng)?shù)臅r候在此阻塞。
  • 檢測(check):setImmediate() 回調(diào)函數(shù)在這里執(zhí)行攘已。
  • 關(guān)閉的回調(diào)函數(shù)(close callbacks):一些關(guān)閉的回調(diào)函數(shù)炮赦,如:socket.on('close', ...)。

在每次運行的事件循環(huán)之間样勃,Node.js 檢查它是否在等待任何異步 I/O 或計時器吠勘,如果沒有的話性芬,則完全關(guān)閉。

階段的詳細概述

定時器

計時器指定 可以執(zhí)行所提供回調(diào) 的 閾值剧防,而不是用戶希望其執(zhí)行的確切時間植锉。在指定的一段時間間隔后, 計時器回調(diào)將被盡可能早地運行峭拘。但是俊庇,操作系統(tǒng)調(diào)度或其它正在運行的回調(diào)可能會延遲它們。

注意輪詢 階段 控制何時定時器執(zhí)行鸡挠。

例如辉饱,假設(shè)您調(diào)度了一個在 100 毫秒后超時的定時器,然后您的腳本開始異步讀取會耗費 95 毫秒的文件:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

當(dāng)事件循環(huán)進入 輪詢 階段時拣展,它有一個空隊列(此時 fs.readFile() 尚未完成)彭沼,因此它將等待剩下的毫秒數(shù),直到達到最快的一個計時器閾值為止备埃。當(dāng)它等待 95 毫秒過后時姓惑,fs.readFile() 完成讀取文件,它的那個需要 10 毫秒才能完成的回調(diào)按脚,將被添加到 輪詢 隊列中并執(zhí)行于毙。當(dāng)回調(diào)完成時,隊列中不再有回調(diào)辅搬,因此事件循環(huán)機制將查看最快到達閾值的計時器唯沮,然后將回到 計時器 階段,以執(zhí)行定時器的回調(diào)伞辛。在本示例中烂翰,您將看到調(diào)度計時器到它的回調(diào)被執(zhí)行之間的總延遲將為 105 毫秒夯缺。

注意:為了防止 輪詢 階段餓死事件循環(huán)蚤氏,libuv(實現(xiàn) Node.js 事件循環(huán)和平臺的所有異步行為的 C 函數(shù)庫),在停止輪詢以獲得更多事件之前踊兜,還有一個硬性最大值(依賴于系統(tǒng))竿滨。

掛起的回調(diào)函數(shù)(pending callbacks)

此階段對某些系統(tǒng)操作(如 TCP 錯誤類型)執(zhí)行回調(diào)。例如捏境,如果 TCP 套接字在嘗試連接時接收到 ECONNREFUSED于游,則某些 *nix 的系統(tǒng)希望等待報告錯誤。這將被排隊以在 掛起的回調(diào) 階段執(zhí)行垫言。

輪詢

輪詢 階段有兩個重要的功能:

  1. 計算應(yīng)該阻塞和輪詢 I/O 的時間贰剥。
  2. 然后,處理 輪詢 隊列里的事件筷频。

當(dāng)事件循環(huán)進入 輪詢 階段且 沒有被調(diào)度的計時器時 蚌成,將發(fā)生以下兩種情況之一:

  • 如果 輪詢 隊列 不是空的 前痘,事件循環(huán)將循環(huán)訪問回調(diào)隊列并同步執(zhí)行它們,直到隊列已用盡担忧,或者達到了與系統(tǒng)相關(guān)的硬性限制芹缔。

  • 如果 輪詢 隊列 是空的 ,還有兩件事發(fā)生:

    • 如果腳本被 setImmediate() 調(diào)度瓶盛,則事件循環(huán)將結(jié)束 輪詢 階段最欠,并繼續(xù) 檢查 階段以執(zhí)行那些被調(diào)度的腳本。
    • 如果腳本 未被 setImmediate()調(diào)度惩猫,則事件循環(huán)將等待回調(diào)被添加到隊列中芝硬,然后立即執(zhí)行。

一旦 輪詢 隊列為空帆锋,事件循環(huán)將檢查 已達到時間閾值的計時器吵取。如果一個或多個計時器已準(zhǔn)備就緒,則事件循環(huán)將繞回計時器階段以執(zhí)行這些計時器的回調(diào)

檢查階段

此階段允許人員在輪詢階段完成后立即執(zhí)行回調(diào)锯厢。如果輪詢階段變?yōu)榭臻e狀態(tài)皮官,并且腳本使用 setImmediate() 后被排列在隊列中,則事件循環(huán)可能繼續(xù)到 檢查 階段而不是等待实辑。

setImmediate() 實際上是一個在事件循環(huán)的單獨階段運行的特殊計時器捺氢。它使用一個 libuv API 來安排回調(diào)在 輪詢 階段完成后執(zhí)行。

通常剪撬,在執(zhí)行代碼時摄乒,事件循環(huán)最終會命中輪詢階段,在那等待傳入連接残黑、請求等馍佑。但是,如果回調(diào)已使用 setImmediate()調(diào)度過梨水,并且輪詢階段變?yōu)榭臻e狀態(tài)拭荤,則它將結(jié)束此階段,并繼續(xù)到檢查階段而不是繼續(xù)等待輪詢事件

以上事件循環(huán)過程的內(nèi)容來自 Node.js 官網(wǎng)

微任務(wù)

熟悉瀏覽器事件循環(huán)的都知道疫诽,每次宏任務(wù)執(zhí)行完畢都會執(zhí)行微任務(wù)隊列里的任務(wù)舅世。同樣 Node.js 也有微任務(wù),只不過它有兩個微任務(wù)隊列奇徒,分別是 process.nextTick 與 Promise雏亚。

每個階段執(zhí)行完畢都會去執(zhí)行 process.nextTick 隊列內(nèi)的任務(wù)與 promise 隊列內(nèi)的任務(wù)。process.nextTick 的優(yōu)先級高于 promise摩钙。

距離說明

Promise.resolve().then(() => console.log('promise'))

process.nextTick(() => console.log('nextTick'))

// 輸出結(jié)果

// nextTick
// promise

以上就是 Node.js 的一些基礎(chǔ)概念罢低。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胖笛,隨后出現(xiàn)的幾起案子网持,更是在濱河造成了極大的恐慌宜肉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翎碑,死亡現(xiàn)場離奇詭異谬返,居然都是意外死亡,警方通過查閱死者的電腦和手機日杈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門遣铝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人莉擒,你說我怎么就攤上這事酿炸。” “怎么了涨冀?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵填硕,是天一觀的道長。 經(jīng)常有香客問我鹿鳖,道長扁眯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任翅帜,我火速辦了婚禮姻檀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涝滴。我一直安慰自己绣版,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布歼疮。 她就那樣靜靜地躺著杂抽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪韩脏。 梳的紋絲不亂的頭發(fā)上缩麸,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音骤素,去河邊找鬼匙睹。 笑死愚屁,一個胖子當(dāng)著我的面吹牛济竹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霎槐,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼送浊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丘跌?” 一聲冷哼從身側(cè)響起袭景,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤唁桩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耸棒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荒澡,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年与殃,在試婚紗的時候發(fā)現(xiàn)自己被綠了单山。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡幅疼,死狀恐怖米奸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爽篷,我是刑警寧澤悴晰,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站逐工,受9級特大地震影響铡溪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泪喊,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一佃却、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窘俺,春花似錦饲帅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至对途,卻和暖如春赦邻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背实檀。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工惶洲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膳犹。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓恬吕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親须床。 傳聞我的和親對象是個殘疾皇子铐料,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349