真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例

小編給大家分享一下JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!

成都創(chuàng)新互聯(lián)公司從2013年創(chuàng)立,先為烏當?shù)确?wù)建站,烏當?shù)鹊仄髽I(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為烏當企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

1、說明

nodejs是單線程執(zhí)行的,同時它又是基于事件驅(qū)動的非阻塞IO編程模型。這就使得我們不用等待異步操作結(jié)果返回,就可以繼續(xù)往下執(zhí)行代碼。當異步事件觸發(fā)之后,就會通知主線程,主線程執(zhí)行相應(yīng)事件的回調(diào)。

本篇文章講解node中JavaScript的代碼的執(zhí)行流程,下面是測試代碼,如果你知道輸出的結(jié)果,那么就不需要再看本篇文章,如果不知道輸出結(jié)果,那么本片文章可幫助你了解:

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})

復(fù)雜的:

setTimeout(() => {
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})
  .then(() => { console.log('5') })
  setTimeout(() => { 
    console.log('6')
    setTimeout(() => {
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)
  })
  setTimeout(() => { console.log('13') }, 0)
})
setTimeout(() => { console.log('14') }, 0)
new Promise((resolve) => { console.log('15'); resolve() })
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })
.then(() => { console.log('18') })

2. nodejs的啟動過程

node.js啟動過程可以分為以下步驟:

  1. 調(diào)用platformInit方法 ,初始化 nodejs 的運行環(huán)境。
  2. 調(diào)用 performance_node_start 方法,對 nodejs 進行性能統(tǒng)計。
  3. openssl設(shè)置的判斷。
  4. 調(diào)用v8_platform.Initialize,初始化 libuv 線程池。
  5. 調(diào)用 V8::Initialize,初始化 V8 環(huán)境。
  6. 創(chuàng)建一個nodejs運行實例。
  7. 啟動上一步創(chuàng)建好的實例。
  8. 開始執(zhí)行js文件,同步代碼執(zhí)行完畢后,進入事件循環(huán)。
  9. 在沒有任何可監(jiān)聽的事件時,銷毀 nodejs 實例,程序執(zhí)行完畢。

JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例

3. nodejs的事件循環(huán)詳解

Nodejs 將消息循環(huán)又細分為 6 個階段(官方叫做 Phase), 每個階段都會有一個類似于隊列的結(jié)構(gòu), 存儲著該階段需要處理的回調(diào)函數(shù).

Nodejs 為了防止某個 階段 任務(wù)太多, 導(dǎo)致后續(xù)的 階段 發(fā)生饑餓的現(xiàn)象, 所以消息循環(huán)的每一個迭代(iterate) 中, 每個 階段 執(zhí)行回調(diào)都有個最大數(shù)量. 如果超過數(shù)量的話也會強行結(jié)束當前 階段而進入下一個 階段. 這一條規(guī)則適用于消息循環(huán)中的每一個 階段.

3.1 Timer 階段

這是消息循環(huán)的第一個階段, 用一個 for 循環(huán)處理所有 setTimeoutsetInterval 的回調(diào).

這些回調(diào)被保存在一個最小堆(min heap) 中. 這樣引擎只需要每次判斷頭元素, 如果符合條件就拿出來執(zhí)行, 直到遇到一個不符合條件或者隊列空了, 才結(jié)束 Timer Phase.

Timer 階段中判斷某個回調(diào)是否符合條件的方法也很簡單. 消息循環(huán)每次進入 Timer 的時候都會保存一下當時的系統(tǒng)時間,然后只要看上述最小堆中的回調(diào)函數(shù)設(shè)置的啟動時間是否超過進入 Timer 時保存的時間, 如果超過就拿出來執(zhí)行.

3.2 Pending I/O Callback 階段

執(zhí)行除了close callbacks、setTimeout()、setInterval()setImmediate()回調(diào)之外幾乎所有回調(diào),比如說TCP連接發(fā)生錯誤、 fs.read, socket 等 IO 操作的回調(diào)函數(shù), 同時也包括各種 error 的回調(diào).

3.3 Idle, Prepare 階段

系統(tǒng)內(nèi)部的一些調(diào)用。

3.4 Poll 階段,重要階段

這是整個消息循環(huán)中最重要的一個 階段, 作用是等待異步請求和數(shù)據(jù),因為它支撐了整個消息循環(huán)機制.

poll階段有兩個主要的功能:一是執(zhí)行下限時間已經(jīng)達到的timers的回調(diào),一是處理poll隊列里的事件。
注:Node的很多API都是基于事件訂閱完成的,比如fs.readFile,這些回調(diào)應(yīng)該都在poll階段完成。

當事件循環(huán)進入poll階段:

  • poll隊列不為空的時候,事件循環(huán)肯定是先遍歷隊列并同步執(zhí)行回調(diào),直到隊列清空或執(zhí)行回調(diào)數(shù)達到系統(tǒng)上限。
  • poll隊列為空的時候,這里有兩種情況。

    • 如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào),那么事件循環(huán)直接結(jié)束poll階段進入check階段來執(zhí)行check隊列里的回調(diào)。
    • 如果代碼沒有被設(shè)定setImmediate()設(shè)定回調(diào):

      • 如果有被設(shè)定的timers,那么此時事件循環(huán)會檢查timers,如果有一個或多個timers下限時間已經(jīng)到達,那么事件循環(huán)將繞回timers階段,并執(zhí)行timers的有效回調(diào)隊列。
      • 如果沒有被設(shè)定timers,這個時候事件循環(huán)是阻塞在poll階段等待事件回調(diào)被加入poll隊列。

Poll階段,當js層代碼注冊的事件回調(diào)都沒有返回的時候,事件循環(huán)會暫時阻塞在poll階段,解除阻塞的條件:

  1. 在poll階段執(zhí)行的時候,會傳入一個timeout超時時間,該超時時間就是poll階段的最大阻塞時間。
  2. timeout時間未到的時候,如果有事件返回,就執(zhí)行該事件注冊的回調(diào)函數(shù)。timeout超時時間到了,則退出poll階段,執(zhí)行下一個階段。

這個 timeout 設(shè)置為多少合適呢? 答案就是 Timer Phase 中最近要執(zhí)行的回調(diào)啟動時間到現(xiàn)在的差值, 假設(shè)這個差值是 detal. 因為 Poll Phase 后面沒有等待執(zhí)行的回調(diào)了. 所以這里最多等待 delta 時長, 如果期間有事件喚醒了消息循環(huán), 那么就繼續(xù)下一個 Phase 的工作; 如果期間什么都沒發(fā)生, 那么到了 timeout 后, 消息循環(huán)依然要進入后面的 Phase, 讓下一個迭代的 Timer Phase 也能夠得到執(zhí)行.
Nodejs 就是通過 Poll Phase, 對 IO 事件的等待和內(nèi)核異步事件的到達來驅(qū)動整個消息循環(huán)的.

3.5 Check  階段

這個階段只處理 setImmediate 的回調(diào)函數(shù).
那么為什么這里要有專門一個處理 setImmediate 的 階段 呢? 簡單來說, 是因為 Poll 階段可能設(shè)置一些回調(diào), 希望在 Poll 階段 后運行. 所以在 Poll 階段 后面增加了這個 Check 階段.

3.6 Close Callbacks 階段

專門處理一些 close 類型的回調(diào). 比如 socket.on('close', ...). 用于資源清理.

4. nodejs執(zhí)行JS代碼過程及事件循環(huán)過程

1、node初始化

初始化node環(huán)境

執(zhí)行輸入的代碼

執(zhí)行process.nextTick回調(diào)

執(zhí)行微任務(wù)(microtasks)

2、進入事件循環(huán)

2.1、進入Timer階段

  • 檢查Timer隊列是否有到期的Timer的回調(diào),如果有,將到期的所有Timer回調(diào)按照TimerId升序執(zhí)行
  • 檢查是否有process.nextTick任務(wù),如果有,全部執(zhí)行
  • 檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行
  • 退出該階段

2.2、進入Pending I/O Callback階段

  • 檢查是否有Pending I/O Callback的回調(diào),如果有,執(zhí)行回調(diào)。如果沒有退出該階段
  • 檢查是否有process.nextTick任務(wù),如果有,全部執(zhí)行
  • 檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行
  • 退出該階段

2.3、進入idle,prepare階段

這個階段與JavaScript關(guān)系不大,略過

2.4、進入Poll階段

首先檢查是否存在尚未完成的回調(diào),如果存在,分如下兩種情況:

第一種情況:有可執(zhí)行的回調(diào)

執(zhí)行所有可用回調(diào)(包含到期的定時器還有一些IO事件等)

檢查是否有process.nextTick任務(wù),如果有,全部執(zhí)行

檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行

退出該階段

第二種情況:沒有可執(zhí)行的回調(diào)

檢查是否有immediate回調(diào),如果有,退出Poll階段。如果沒有,阻塞在此階段,等待新的事件通知

如果不存在尚未完成的回調(diào),退出Poll階段

2.5、進入check階段

如果有immediate回調(diào),則執(zhí)行所有immediate回調(diào)

檢查是否有process.nextTick任務(wù),如果有,全部執(zhí)行

檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行

退出該階段

2.6、進入closing階段

如果有immediate回調(diào),則執(zhí)行所有immediate回調(diào)

檢查是否有process.nextTick任務(wù),如果有,全部執(zhí)行

檢查是否有微任務(wù)(promise),如果有,全部執(zhí)行

退出該階段

3、檢查是否有活躍的handles(定時器、IO等事件句柄)

如果有,繼續(xù)下一輪事件循環(huán)

如果沒有,結(jié)束事件循環(huán),退出程序

注意:

事件循環(huán)的每一個子階段退出之前都會按順序執(zhí)行如下過程:

檢查是否有 process.nextTick 回調(diào),如果有,全部執(zhí)行。

檢查是否有 微任務(wù)(promise),如果有,全部執(zhí)行。

4.1 關(guān)于Promise和process.nextTick

事件循環(huán)隊列先保證所有的process.nextTick回調(diào),然后將所有的Promise回調(diào)追加在后面,最終在每個階段結(jié)束的時候一次性拿出來執(zhí)行。

此外,process.nextTickPromise回調(diào)的數(shù)量是受限制的,也就是說,如果一直往這個隊列中加入回調(diào),那么整個事件循環(huán)就會被卡住。

JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例

4.2 關(guān)于setTimeout(…, 0) 和 setImmediate

這兩個方法的回調(diào)到底誰快?

如下面的例子:

setImmediate(() => console.log(2))
setTimeout(() => console.log(1))

使用nodejs多次執(zhí)行后,發(fā)現(xiàn)輸出結(jié)果有時是1 2,有時是2 1。

對于多次執(zhí)行輸出結(jié)果不同,需要了解事件循環(huán)的基礎(chǔ)問題。

首先,Nodejs啟動,初始化環(huán)境后加載我們的JS代碼(index.js).發(fā)生了兩件事(此時尚未進入消息循環(huán)環(huán)節(jié)):

setImmediate 向 Check 階段 中添加了回調(diào) console.log(2);

setTimeout 向 Timer 階段 中添加了回調(diào) console.log(1)

這時候, 要初始化階段完畢, 要進入 Nodejs 消息循環(huán)了。

為什么會有兩種輸出呢? 接下來一步很關(guān)鍵:

當執(zhí)行到 Timer 階段 時, 會發(fā)生兩種可能. 因為每一輪迭代剛剛進入 Timer 階段 時會取系統(tǒng)時間保存起來, 以 ms(毫秒) 為最小單位.

如果 Timer 階段 中回調(diào)預(yù)設(shè)的時間 > 消息循環(huán)所保存的時間, 則執(zhí)行 Timer 階段 中的該回調(diào). 這種情況下先輸出 1, 直到 Check 階段 執(zhí)行后,輸出2.總的來說, 結(jié)果是 1 2.

如果運行比較快, Timer 階段 中回調(diào)預(yù)設(shè)的時間可能剛好等于消息循環(huán)所保存的時間, 這種情況下, Timer 階段 中的回調(diào)得不到執(zhí)行, 則繼續(xù)下一個 階段. 直到 Check 階段, 輸出 2. 然后等下一輪迭代的 Timer 階段, 這時的時間一定是滿足 Timer 階段 中回調(diào)預(yù)設(shè)的時間 > 消息循環(huán)所保存的時間 , 所以 console.log(1) 得到執(zhí)行, 輸出 1. 總的來說, 結(jié)果就是 2 1.

所以, 輸出不穩(wěn)定的原因就取決于進入 Timer 階段 的時間是否和執(zhí)行 setTimeout 的時間在 1ms 內(nèi). 如果把代碼改成如下, 則一定會得到穩(wěn)定的輸出:

require('fs').readFile('my-file-path.txt', () => {
 setImmediate(() => console.log(2))
 setTimeout(() => console.log(1))
});

這是因為消息循環(huán)在 Pneding I/O Phase 才向 Timer 和 Check 隊列插入回調(diào). 這時按照消息循環(huán)的執(zhí)行順序, Check 一定在 Timer 之前執(zhí)行。

從性能角度講, setTimeout 的處理是在 Timer Phase, 其中 min heap 保存了 timer 的回調(diào), 因此每執(zhí)行一個回調(diào)的同時都會涉及到堆調(diào)整. 而 setImmediate 僅僅是清空一個隊列. 效率自然會高很多.

再從執(zhí)行時機上講. setTimeout(..., 0) 和 setImmediate 完全屬于兩個階段.

5. 一個實際例子演示

下面以一段代碼來說明nodejs運行JavaScript的機制。

如下面一段代碼:

setTimeout(() => {                                                // settimeout1
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })      // Promise3
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})         // Promise4
  .then(() => { console.log('5') })
  setTimeout(() => {                                              // settimeout3
    console.log('6')
    setTimeout(() => {                                            // settimeout5
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })   // Promise5
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })  // Promise6
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)                    // settimeout6
  })
  setTimeout(() => { console.log('13') }, 0)                      // settimeout4
})
setTimeout(() => { console.log('14') }, 0)                        // settimeout2
new Promise((resolve) => { console.log('15'); resolve() })        // Promise1
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })        // Promise2
.then(() => { console.log('18') })

上面代碼執(zhí)行過程:

node初始化

執(zhí)行JavaScript代碼

遇到setTimeout, 把回調(diào)函數(shù)放到Timer隊列中,記為settimeout1

遇到setTimeout, 把回調(diào)函數(shù)放到Timer隊列中,記為settimeout2

遇到Promise,執(zhí)行,輸出15,把回調(diào)函數(shù)放到微任務(wù)隊列,記為Promise1

遇到Promise,執(zhí)行,輸出17,把回調(diào)函數(shù)放到微任務(wù)隊列,記為Promise2

代碼執(zhí)行結(jié)束,此階段輸出結(jié)果:15 17

沒有process.nextTick回調(diào),略過

執(zhí)行微任務(wù)

檢查微任務(wù)隊列是否有可執(zhí)行回調(diào),此時隊列有2個回調(diào):Promise1、Promise2

執(zhí)行Promise1回調(diào),輸出16

執(zhí)行Promise2回調(diào),輸出18

此階段輸出結(jié)果:16 18

進入第一次事件循環(huán)

進入Timer階段

檢查Timer隊列是否有可執(zhí)行的回調(diào),此時隊列有2個回調(diào):settimeout1、settimeout2

執(zhí)行settimeout1回調(diào):

輸出1、2、4

添加了2個微任務(wù),記為Promise3、Promise4

添加了2個Timer任務(wù),記為settimeout3、settimeout4

執(zhí)行settimeout2回調(diào),輸出14

Timer隊列任務(wù)執(zhí)行完畢

沒有process.nextTick回調(diào),略過

檢查微任務(wù)隊列是否有可執(zhí)行回調(diào),此時隊列有2個回調(diào):Promise3、Promise4

按順序執(zhí)行2個微任務(wù),輸出3、5

此階段輸出結(jié)果:1 2 4 14 3 5

Pending I/O Callback階段沒有任務(wù),略過

進入 Poll 階段

檢查是否存在尚未完成的回調(diào),此時有2個回調(diào):settimeout3、settimeout4

執(zhí)行settimeout3回調(diào)

輸出6

添加了2個Timer任務(wù),記為settimeout5、settimeout6

執(zhí)行settimeout4回調(diào),輸出13

沒有process.nextTick回調(diào),略過

沒有微任務(wù),略過

此階段輸出結(jié)果:6 13

check、closing階段沒有任務(wù),略過

檢查是否還有活躍的handles(定時器、IO等事件句柄),有,繼續(xù)下一輪事件循環(huán)

進入第二次事件循環(huán)

進入Timer階段

檢查Timer隊列是否有可執(zhí)行的回調(diào),此時隊列有2個回調(diào):settimeout5、settimeout6

執(zhí)行settimeout5回調(diào):

輸出7、 8、10

添加了2個微任務(wù),記為Promise5、Promise6

執(zhí)行settimeout6回調(diào),輸出12

沒有process.nextTick回調(diào),略過

檢查微任務(wù)隊列是否有可執(zhí)行回調(diào),此時隊列有2個回調(diào):Promise5、Promise6

按順序執(zhí)行2個微任務(wù),輸出9、11

此階段輸出結(jié)果:7 8 10 12 9 11

Pending I/O Callback、Poll、check、closing階段沒有任務(wù),略過

檢查是否還有活躍的handles(定時器、IO等事件句柄),沒有了,結(jié)束事件循環(huán),退出程序

程序執(zhí)行結(jié)束,輸出結(jié)果:15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11

JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例

看完了這篇文章,相信你對JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例有了一定的了解,想了解更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!


網(wǎng)站題目:JavaScript在nodejs環(huán)境下執(zhí)行機制和事件循環(huán)的示例
新聞來源:http://weahome.cn/article/poipgc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部