亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Node異步和事件循環(huán)的深入講解

 更新時間:2022年07月20日 16:50:27   作者:Emma_  
異步對于前端來說是老生常談的話題,同樣學(xué)習(xí)node也離不開異步IO與事件循環(huán),下面這篇文章主要給大家介紹了關(guān)于Node異步和事件循環(huán)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

Node 最初是為打造高性能的 Web 服務(wù)器而生,作為 JavaScript 的服務(wù)端運行時,具有事件驅(qū)動、異步 I/O、單線程等特性?;谑录h(huán)的異步編程模型使 Node 具備處理高并發(fā)的能力,極大地提升服務(wù)器的性能,同時,由于保持了 JavaScript 單線程的特點,Node 不需要處理多線程下狀態(tài)同步、死鎖等問題,也沒有線程上下文切換所帶來的性能上的開銷。基于這些特性,使 Node  具備高性能、高并發(fā)的先天優(yōu)勢,并可基于它構(gòu)建各種高速、可伸縮網(wǎng)絡(luò)應(yīng)用平臺。

本文將深入 Node 異步和事件循環(huán)的底層實現(xiàn)和執(zhí)行機(jī)制,希望對你有所幫助。

為什么要異步?

Node 為什么要使用異步來作為核心編程模型呢?

前面說過,Node 最初是為打造高性能的 Web 服務(wù)器而生,假設(shè)業(yè)務(wù)場景中有幾組互不相關(guān)的任務(wù)要完成,現(xiàn)代主流的解決方式有以下兩種:

  • 單線程串行依次執(zhí)行。

  • 多線程并行完成。

單線程串行依次執(zhí)行,是一種同步的編程模型,它雖然比較符合程序員按順序思考的思維方式,易寫出更順手的代碼,但由于是同步執(zhí)行 I/O,同一時刻只能處理單個請求,會導(dǎo)致服務(wù)器響應(yīng)速度較慢,無法在高并發(fā)的應(yīng)用場景下適用,且由于是阻塞 I/O,CPU 會一直等待 I/O 完成,無法做其他事情,使 CPU 的處理能力得不到充分利用,最終導(dǎo)致效率的低下,

而多線程的編程模型也會因為編程中的狀態(tài)同步、死鎖等問題讓開發(fā)人員頭疼。盡管多線程在多核 CPU 上能夠有效提升 CPU 的利用率。

雖然單線程串行依次執(zhí)行和多線程并行完成的編程模型有其自身的優(yōu)勢,但是在性能、開發(fā)難度等方面也有不足之處。

除此之外,從響應(yīng)客戶端請求的速度出發(fā),如果客戶端同時獲取兩個資源,同步方式的響應(yīng)速度會是兩個資源的響應(yīng)速度之和,而異步方式的響應(yīng)速度會是兩者中最大的一個,性能優(yōu)勢相比同步十分明顯。隨著應(yīng)用復(fù)雜度的增加,該場景會演變成同時響應(yīng) n 個請求,異步相比于同步的優(yōu)勢將會凸顯出來。

綜上所述,Node 給出了它的答案:利用單線程,遠(yuǎn)離多線程死鎖、狀態(tài)同步等問題;利用異步 I/O,讓單線程遠(yuǎn)離阻塞,以更好地使用 CPU。這就是 Node 使用異步作為核心編程模型的原因。

此外,為了彌補(bǔ)單線程無法利用多核 CPU 的缺點,Node 也提供了類似瀏覽器中 Web Workers 的子進(jìn)程,該子進(jìn)程可以通過工作進(jìn)程高效地利用 CPU。

如何實現(xiàn)異步?

聊完了為什么要使用異步,那要如何實現(xiàn)異步呢?

我們通常所說的異步操作總共有兩類:一是像文件 I/O、網(wǎng)絡(luò) I/O 這類與 I/O 有關(guān)的操作;二是像 setTimeOut、setInterval 這類與 I/O 無關(guān)的操作。很明顯我們所討論的異步是指與 I/O 有關(guān)的操作,即異步 I/O。

異步 I/O 的提出是期望 I/O 的調(diào)用不會阻塞后續(xù)程序的執(zhí)行,將原有等待 I/O 完成的這段時間分配給其余需要的業(yè)務(wù)去執(zhí)行。要達(dá)到這個目的,就需要用到非阻塞 I/O。

阻塞 I/O 是 CPU 在發(fā)起 I/O 調(diào)用后,會一直阻塞,等待 I/O 完成。知道了阻塞 I/O,非阻塞 I/O 就很好理解了,CPU 在發(fā)起 I/O 調(diào)用后會立即返回,而不是阻塞等待,在 I/O 完成之前,CPU 可以處理其他事務(wù)。顯然,相比于阻塞 I/O,非阻塞 I/O 多于性能的提升是很明顯的。

那么,既然使用了非阻塞 I/O,CPU 在發(fā)起 I/O 調(diào)用后可以立即返回,那它是如何知道 I/O 完成的呢?答案是輪詢。

為了及時獲取 I/O 調(diào)用的狀態(tài),CPU 會不斷重復(fù)調(diào)用 I/O 操作來確認(rèn) I/O 是否已經(jīng)完成,這種重復(fù)調(diào)用判斷操作是否完成的技術(shù)就叫做輪詢。

顯然,輪詢會讓 CPU 不斷重復(fù)地執(zhí)行狀態(tài)判斷,是對 CPU 資源的浪費。并且,輪詢的間間隔很難控制,如果間隔太長,I/O 操作的完成得不到及時的響應(yīng),間接降低應(yīng)用程序的響應(yīng)速度;如果間隔太短,難免會讓 CPU 花在輪詢的耗時變長,降低 CPU 資源的利用率。

因此,輪詢雖然滿足了非阻塞 I/O 不會阻塞后續(xù)程序的執(zhí)行的要求,但是對于應(yīng)用程序而言,它仍然只能算是一種同步,因為應(yīng)用程序仍然需要等待 I/O 完全返回,依舊花費了很多時間來等待。

我們所期望的完美的異步 I/O,應(yīng)該是應(yīng)用程序發(fā)起非阻塞調(diào)用,無須通過輪詢的方式不斷查詢 I/O 調(diào)用的狀態(tài),而是可以直接處理下一個任務(wù),在 I/O 完成后通過信號量或回調(diào)將數(shù)據(jù)傳遞給應(yīng)用程序即可。

如何實現(xiàn)這種異步 I/O 呢?答案是線程池。

雖然本文一直提到,Node 是單線程執(zhí)行的,但此處的單線程是指 JavaScript 代碼是執(zhí)行在單線程上的,對于 I/O 操作這類與主業(yè)務(wù)邏輯無關(guān)的部分,通過運行在其他線程的方式實現(xiàn),并不會影響或阻塞主線程的運行,反而可以提高主線程的執(zhí)行效率,實現(xiàn)異步 I/O。

通過線程池,讓主線程僅進(jìn)行 I/O 的調(diào)用,讓其他多個線程進(jìn)行阻塞 I/O 或者非阻塞 I/O 加輪詢技術(shù)完成數(shù)據(jù)獲取,再通過線程之間的通信將 I/O 得到的數(shù)據(jù)進(jìn)行傳遞,這就輕松實現(xiàn)了異步 I/O:

主線程進(jìn)行 I/O 調(diào)用,而線程池進(jìn)行 I/O 操作,完成數(shù)據(jù)的獲取,然后通過線程之間的通信將數(shù)據(jù)傳遞給主線程,即可完成一次 I/O 的調(diào)用,主線程再利用回調(diào)函數(shù),將數(shù)據(jù)暴露給用戶,用戶再利用這些數(shù)據(jù)來完成業(yè)務(wù)邏輯層面的操作,這就是 Node 中一次完整的異步 I/O 流程。而對于用戶來說,不必在意底層這些繁瑣的實現(xiàn)細(xì)節(jié),只需要調(diào)用 Node 封裝好的異步 API,并傳入處理業(yè)務(wù)邏輯的回調(diào)函數(shù)即可,如下所示:

const fs = require("fs");

fs.readFile('example.js', (data) => {
  // 進(jìn)行業(yè)務(wù)邏輯的處理
});

Node 的異步底層實現(xiàn)機(jī)制在不同平臺下有所不同:Windows 下主要通過 IOCP 來向系統(tǒng)內(nèi)核發(fā)送 I/O 調(diào)用和從內(nèi)核獲取已完成的 I/O 操作,配以事件循環(huán),以此完成異步 I/O 的過程;Linux 下通過 epoll 實現(xiàn)這個過程;FreeBSD下通過 kqueue 實現(xiàn),Solaris 下通過 Event ports 實現(xiàn)。線程池在 Windows 下由內(nèi)核(IOCP)直接提供,*nix 系列則由 libuv 自行實現(xiàn)。

由于 Windows 平臺和 *nix 平臺的差異,Node 提供了 libuv 作為抽象封裝層,使得所有平臺兼容性的判斷都由這一層來完成,保證上層的 Node 與下層的自定義線程池及 IOCP 之間各自獨立。Node 在編譯期間會判斷平臺條件,選擇性編譯 unix 目錄或是 win 目錄下的源文件到目標(biāo)程序中:

以上就是 Node 對異步的實現(xiàn)。

(線程池的大小可以通過環(huán)境變量 UV_THREADPOOL_SIZE 設(shè)置,默認(rèn)值為 4,用戶可結(jié)合實際情況來調(diào)整這個值的大小。)

那么問題來了,在得到線程池傳遞過來的數(shù)據(jù)后,主線程是如何、何時調(diào)用回調(diào)函數(shù)的呢?答案是事件循環(huán)。

基于事件循環(huán)的異步編程模型

既然使用回調(diào)函數(shù)來進(jìn)行對 I/O 數(shù)據(jù)的處理,就必然涉及到何時、如何調(diào)用回調(diào)函數(shù)的問題。在實際開發(fā)中,往往會涉及到多個、多類異步 I/O 調(diào)用的場景,如何合理安排這些異步 I/O 回調(diào)的調(diào)用,確保異步回調(diào)的有序進(jìn)行是一個難題,而且,除了異步 I/O 之外,還存在定時器這類非 I/O 的異步調(diào)用,這類 API 實時性強(qiáng),優(yōu)先級相應(yīng)地更高,如何實現(xiàn)不同優(yōu)先級回調(diào)地調(diào)度呢?

因此,必須存在一個調(diào)度機(jī)制,對不同優(yōu)先級、不同類型的異步任務(wù)進(jìn)行協(xié)調(diào),確保這些任務(wù)在主線程上有條不紊地運行。與瀏覽器一樣,Node 選擇了事件循環(huán)來承擔(dān)這項重任。

Node 根據(jù)任務(wù)的種類和優(yōu)先級將它們分為七類:Timers、Pending、Idle、Prepare、Poll、Check、Close。對于每類任務(wù),都存在一個先進(jìn)先出的任務(wù)隊列來存放任務(wù)及其回調(diào)(Timers 是用小頂堆存放)?;谶@七個類型,Node 將事件循環(huán)的執(zhí)行分為如下七個階段:

timers

這個階段的執(zhí)行優(yōu)先級是最高的。

事件循環(huán)在這個階段會檢查存放定時器的數(shù)據(jù)結(jié)構(gòu)(最小堆),對其中的定時器進(jìn)行遍歷,逐個比較當(dāng)前時間和過期時間,判斷該定時器是否過期,如果過期的話,就將該定時器的回調(diào)函數(shù)取出并執(zhí)行。

pending

該階段會執(zhí)行網(wǎng)絡(luò)、IO 等異常時的回調(diào)。一些 *nix 上報的錯誤,在這個階段會得到處理。另外,一些應(yīng)該在上輪循環(huán)的 poll 階段執(zhí)行的 I/O 回調(diào)會被推遲到這個階段執(zhí)行。

idle、prepare

這兩個階段僅在事件循環(huán)內(nèi)部使用。

poll

檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(除了關(guān)閉回調(diào)、定時器調(diào)度的回調(diào)和 之外幾乎所有回調(diào)setImmediate());節(jié)點會在適當(dāng)?shù)臅r候阻塞在這里。

poll,即輪詢階段是事件循環(huán)最重要的階段,網(wǎng)絡(luò) I/O、文件 I/O 的回調(diào)都主要在這個階段被處理。該階段有兩個主要功能:

  • 計算該階段應(yīng)該阻塞和輪詢 I/O 的時間。

  • 處理 I/O 隊列中的回調(diào)。

當(dāng)事件循環(huán)進(jìn)入 poll 階段并且沒有設(shè)置定時器時:

  • 如果輪詢隊列不為空,則事件循環(huán)將遍歷該隊列,同步地執(zhí)行它們,直到隊列為空或達(dá)到可執(zhí)行的最大數(shù)量。

  • 如果輪詢隊列為空,則會發(fā)生另外兩種情況之一:

    • 如果有 setImmediate() 回調(diào)需要執(zhí)行,則立即結(jié)束 poll 階段,并進(jìn)入 check 階段以執(zhí)行回調(diào)。

    • 如果沒有 setImmediate() 回調(diào)需要執(zhí)行,事件循環(huán)將停留在該階段以等待回調(diào)被添加到隊列中,然后立即執(zhí)行它們。在超時時間到達(dá)前,事件循環(huán)會一直停留等待。之所以選擇停留在這里是因為 Node 主要是處理 IO 的,這樣可以更及時地響應(yīng) IO。

一旦輪詢隊列為空,事件循環(huán)將檢查已達(dá)到時間閾值的定時器。如果有一個或多個定時器達(dá)到時間閾值,事件循環(huán)將回到 timers 階段以執(zhí)行這些定時器的回調(diào)。

check

該階段會依次執(zhí)行 setImmediate() 的回調(diào)。

close

該階段會執(zhí)行一些關(guān)閉資源的回調(diào),如 socket.on('close', ...)。該階段晚點執(zhí)行也影響不大,優(yōu)先級最低。

當(dāng) Node 進(jìn)程啟動時,它會初始化事件循環(huán),執(zhí)行用戶的輸入代碼,進(jìn)行相應(yīng)異步 API 的調(diào)用、計時器的調(diào)度等等,然后開始進(jìn)入事件循環(huán):

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

事件循環(huán)的每一輪循環(huán)(通常被稱為 tick),會按照如上給定的優(yōu)先級順序進(jìn)入七個階段的執(zhí)行,每個階段會執(zhí)行一定數(shù)量的隊列中的回調(diào),之所以只執(zhí)行一定數(shù)量而不全部執(zhí)行完,是為了防止當(dāng)前階段執(zhí)行時間過長,避免下一個階段得不到執(zhí)行。

OK,以上就是事件循環(huán)的基本執(zhí)行流程。現(xiàn)在讓我們來看另外一個問題。

對于以下這個場景:

const server = net.createServer(() => {}).listen(8080);

server.on('listening', () => {});

當(dāng)服務(wù)成功綁定到 8000 端口,即 listen() 成功調(diào)用時,此時 listening 事件的回調(diào)還沒有綁定,因此端口成功綁定后,我們所傳入的 listening 事件的回調(diào)并不會執(zhí)行。

再思考另外一個問題,我們在開發(fā)中可能會有一些需求,如處理錯誤、清理不需要的資源等等優(yōu)先級不是那么高的任務(wù),如果以同步的方式執(zhí)行這些邏輯,就會影響當(dāng)前任務(wù)的執(zhí)行效率;如果以異步的方式,比如以回調(diào)的形式傳入 setImmediate() 又無法保證它們的執(zhí)行時機(jī),實時性不高。那么要如何處理這些邏輯呢?

基于這幾個問題,Node 參考了瀏覽器,也實現(xiàn)了一套微任務(wù)的機(jī)制。在 Node 中,除了調(diào)用 new Promise().then() 所傳入的回調(diào)函數(shù)會被封裝成微任務(wù)外,process.nextTick() 的回調(diào)也會被封裝成微任務(wù),并且后者的執(zhí)行優(yōu)先級比前者高。

有了微任務(wù)后,事件循環(huán)的執(zhí)行流程又是怎么樣的呢?換句話說,微任務(wù)的執(zhí)行時機(jī)在什么時候?

  • 在 node 11 及 11 之后的版本,一旦執(zhí)行完一個階段里的一個任務(wù)就立刻執(zhí)行微任務(wù)隊列,清空該隊列。

  • 在 node11 之前執(zhí)行完一個階段后才開始執(zhí)行微任務(wù)。

因此,有了微任務(wù)后,事件循環(huán)的每一輪循環(huán),會先執(zhí)行 timers 階段的一個任務(wù),然后按照先后順序清空 process.nextTick()new Promise().then() 的微任務(wù)隊列,接著繼續(xù)執(zhí)行 timers 階段的下一個任務(wù)或者下一個階段,即 pending 階段的一個任務(wù),按照這樣的順序以此類推。

利用 process.nextTick(),Node 就可以解決上面的端口綁定問題:在 listen() 方法內(nèi)部,listening 事件的發(fā)出會被封裝成回調(diào)傳入 process.nextTick() 中,如下偽代碼所示:

function listen() {
    // 進(jìn)行監(jiān)聽端口的操作
    ...
    // 將 `listening` 事件的發(fā)出封裝成回調(diào)傳入 `process.nextTick()` 中
    process.nextTick(() => {
        emit('listening');
    });
};

在當(dāng)前代碼執(zhí)行完畢后便會開始執(zhí)行微任務(wù),從而發(fā)出 listening 事件,觸發(fā)該事件回調(diào)的調(diào)用。

一些注意事項

由于異步本身的不可預(yù)知性和復(fù)雜性,在使用 Node 提供的異步 API 的過程中,盡管我們已經(jīng)掌握了事件循環(huán)的執(zhí)行原理,但是仍可能會有一些不符合直覺或預(yù)期的現(xiàn)象產(chǎn)生。

比如定時器(setTimeout、setImmediate)的執(zhí)行順序會因為調(diào)用它們的上下文而有所不同。如果兩者都是從頂層上下文中調(diào)用的,那么它們的執(zhí)行時間取決于進(jìn)程或機(jī)器的性能。

我們來看以下這個例子:

setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

以上代碼的執(zhí)行結(jié)果是什么呢?按照我們剛才對事件循環(huán)的描述,你可能會有這樣的答案:由于 timers 階段會比 check 階段先執(zhí)行,因此 setTimeout() 的回調(diào)會先執(zhí)行,然后再執(zhí)行 setImmediate() 的回調(diào)。

實際上,這段代碼的輸出結(jié)果是不確定的,可能先輸出 timeout,也可能先輸出 immediate。這是因為這兩個定時器都是在全局上下文中調(diào)用的,當(dāng)事件循環(huán)開始運行并執(zhí)行到 timers 階段時,當(dāng)前時間可能大于 1 ms,也可能不足 1 ms,具體取決于機(jī)器的執(zhí)行性能,因此 setTimeout() 在第一個 timers 階段是否會被執(zhí)行實際上是不確定的,因此才會出現(xiàn)不同的輸出結(jié)果。

(當(dāng) delaysetTimeout 的第二個參數(shù))的值大于 2147483647 或小于 1 時, delay 會被設(shè)置為 1。)

我們接著看下面這段代碼:

const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

可以看到,在這段代碼中兩個定時器都被封裝成回調(diào)函數(shù)傳入 readFile 中,很明顯當(dāng)該回調(diào)被調(diào)用時當(dāng)前時間肯定大于 1 ms 了,所以 setTimeout 的回調(diào)會比 setImmediate 的回調(diào)先得到調(diào)用,因此打印結(jié)果為:timeout immediate。

以上是在使用 Node 時需要注意的與定時器相關(guān)的事項。除此之外,還需注意 process.nextTick()new Promise().then() 還有 setImmediate() 的執(zhí)行順序,由于這部分比較簡單,前面已經(jīng)提到過,就不再贅述了。

總結(jié)

文章開篇從為什么要異步、如何實現(xiàn)異步兩個角度出發(fā),較詳細(xì)地闡述了 Node 事件循環(huán)的實現(xiàn)原理,并提到一些需要注意的相關(guān)事項,希望對你有所幫助。

如果覺得這篇文章寫的不錯的話,就請給我點個贊吧!

參考資料

到此這篇關(guān)于Node異步和事件循環(huán)的文章就介紹到這了,更多相關(guān)Node異步和事件循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Express + Node.js實現(xiàn)登錄攔截器的實例代碼

    Express + Node.js實現(xiàn)登錄攔截器的實例代碼

    本篇文章主要介紹了Express + Node.js實現(xiàn)攔截器的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • WebSocket實現(xiàn)簡單客服聊天系統(tǒng)

    WebSocket實現(xiàn)簡單客服聊天系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了WebSocket實現(xiàn)簡單客服聊天系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • 快速掌握Node.js模塊封裝及使用

    快速掌握Node.js模塊封裝及使用

    這篇文章主要為大家詳細(xì)介紹了Node.js模塊封裝及使用,幫助大家快速掌握Node.js模塊封裝及使用,感興趣的小伙伴們可以參考一下
    2016-03-03
  • module.exports和exports使用誤區(qū)案例分析

    module.exports和exports使用誤區(qū)案例分析

    module.exports和exports使用誤區(qū),使用require()模塊時,得到的永遠(yuǎn)都是module.exports指向的對象
    2023-04-04
  • window系統(tǒng) nodejs安裝opencv環(huán)境配置圖文詳解

    window系統(tǒng) nodejs安裝opencv環(huán)境配置圖文詳解

    這篇文章主要介紹了window系統(tǒng) nodejs安裝opencv環(huán)境配置,結(jié)合圖文形式詳細(xì)分析了window環(huán)境下 nodejs安裝opencv的具體步驟、注意事項
    2023-04-04
  • 使用Node.js實現(xiàn)一個多人游戲服務(wù)器引擎

    使用Node.js實現(xiàn)一個多人游戲服務(wù)器引擎

    這篇文章主要給大家介紹了關(guān)于如何使用Node.js實現(xiàn)一個多人游戲服務(wù)器引擎的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Node.js具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 究竟什么是Node.js?Node.js有什么好處?

    究竟什么是Node.js?Node.js有什么好處?

    這篇文章主要介紹了究竟什么是Node.js?Node.js有什么好處?,為試圖解釋什么是 Node.js,本文將簡要介紹一些背景信息:它要解決的問題,它如何工作,如何運行一個簡單應(yīng)用程序,最后,Node 在什么情況下是一個好的解決方案,需要的朋友可以參考下
    2015-05-05
  • 為何從eggjs升級到midwayjs的原因詳解

    為何從eggjs升級到midwayjs的原因詳解

    這篇文章主要為大家介紹了為何從eggjs升級到midwayjs的原因詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • NodeJs操作MongoDB教程之分頁功能以及常見問題

    NodeJs操作MongoDB教程之分頁功能以及常見問題

    這篇文章主要給大家介紹了關(guān)于NodeJs操作MongoDB教程之分頁功能以及常見問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用NodeJs具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 使用node.js 制作網(wǎng)站前臺后臺

    使用node.js 制作網(wǎng)站前臺后臺

    本文給大家介紹實用node.js 制作網(wǎng)站前臺和后臺,非常的詳盡,有需要的朋友可以參考下
    2014-11-11

最新評論