這篇文章帶給大家的內容是關於JavaScript高階函數的用法介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
1、說明
nodejs是單執行緒執行的,同時它又是基於事件驅動的非阻塞IO程式設計模型。這就使得我們不用等待非同步操作結果返回,就可以繼續往下執行程式碼。當非同步事件觸發之後,就會通知主線程,主線程執行對應事件的回呼。
這篇文章說明node中JavaScript的程式碼的執行流程,以下是測試程式碼,如果你知道輸出的結果,那麼就不需要再看本篇文章,如果不知道輸出結果,那麼本片文章可幫助你了解:
console.log(1) setTimeout(function () { new Promise(function (resolve) { console.log(2) resolve() }) .then(() => { console.log(3) }) }) setTimeout(function () { console.log(4) })
複雜的:
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啟動過程可以分為以下步驟:3. nodejs的事件循環詳解
Nodejs 將訊息循環又細分為6 個階段(官方叫做Phase), 每個階段都會有一個類似於隊列的結構, 存儲著該階段需要處理的回調函數.Nodejs 為了防止某個階段任務太多, 導致後續的階段發生飢餓的現象, 所以訊息循環的每一個迭代(iterate) 中, 每個階段執行回調都有個最大數量. 如果超過數量的話也會強行結束當前階段而進入下一個階段. 這條規則適用於訊息循環中的每一個階段.3.1 Timer 階段這是訊息循環的第一個階段, 用一個for 循環處理所有
setTimeout 和
setInterval 的回呼.
close callbacks、
setTimeout()、
setInterval()、
setImmediate()回呼之外幾乎所有回調,比如說
TCP連線發生錯誤、
fs.read,
socket 等IO 操作的回呼函數, 同時也包含各種error 的回呼.
poll階段有兩個主要的功能:一是執行下限時間已經達到的timers的回調,一是處理poll佇列裡的事件。
註:Node的許多API都是基於事件訂閱完成的,例如fs.readFile,這些回呼應該都在poll階段完成。
佇列不為空的時候,事件循環肯定是先遍歷佇列並同步執行回調,直到佇列清空或執行回呼數達到系統上限。
poll佇列為空的時候,這裡有兩種情況。
設定了回調,那麼事件循環直接結束
poll階段進入
check階段來執行
check佇列裡的回呼。
setImmediate()設定回呼:
Poll階段,js層程式碼註冊的事件回呼都沒有回傳的時候,事件循環會暫時阻塞在poll階段,解除封鎖的條件:
- 在poll階段執行的時候,會傳入一個timeout逾時時間,而這個逾時時間就是poll階段的最大阻塞時間。
- timeout時間未到的時候,如果有事件返回,就執行該事件註冊的回呼函數。 timeout逾時時間到了,則退出poll階段,執行下一個階段。
這個timeout 設定為多少合適呢? 答案就是Timer Phase 中最近要執行的回呼啟動時間到現在的差值, 假設這個差值是detal. 因為Poll Phase 後面沒有等待執行的回調了. 所以這裡最多等待delta 時長, 如果期間有事件喚醒了消息循環, 那麼就繼續下一個Phase 的工作; 如果期間什麼都沒發生, 那麼到了timeout 後, 消息循環依然要進入後面的Phase, 讓下一個迭代的Timer Phase 也能夠得到執行.
Nodejs 就是透過Poll Phase, 對IO 事件的等待和內核非同步事件的到達來驅動整個訊息循環的.
這個階段只處理setImmediate 的回呼函數.
那麼為什麼這裡要有專門一個處理setImmediate 的階段呢? 簡單來說, 是因為Poll 階段可能設定一些回呼, 希望在Poll 階段後運行. 所以在Poll 階段後面增加了這個Check 階段.
專門處理一些close 類型的回調. 例如socket.on('close', .. .)
. 用於資源清理.
1、node初始化
初始化node環境
執行輸入的程式碼
執行process.nextTick
回呼
執行微任務(microtasks)
2、進入事件循環
#2.1、進入Timer
階段
Timer
佇列是否有到期的Timer
的回調,如果有,將到期的所有Timer
回調依照TimerId
升序執行process.nextTick
任務,如果有,全部執行2.2、進入Pending I/O Callback
#階段
Pending I/O Callback
的回調,如果有,執行回呼。 如果沒有退出該階段
process.nextTick
任務,如果有,全部執行2.3、進入idle,prepare
階段
這個階段與JavaScript關係不大,略過
2.4、進入Poll
階段
首先檢查是否存在尚未完成的回調,如果存在,分如下兩種情況:
第一種情況:有可執行的回呼
執行所有可用回調(包含到期的定時器還有一些IO事件等)
檢查是否有process.nextTick
任務,如果有,全部執行
檢查是否有微任務(promise),如果有,全部執行
退出該階段
第二種情況:沒有可執行的回呼
檢查是否有immediate
回呼,如果有,退出Poll階段。如果沒有,阻塞在此階段,等待新的事件通知
check階段
process.nextTick任務,如果有,全部執行
closing階段
process.nextTick任務,如果有,全部執行
handles(定時器、IO等事件句柄)
注意:
事件循環的每個子階段退出之前都會按順序執行如下程序:檢查是否有process.nextTick 回調,如果有,全部執行。事件循环队列先保证所有的process.nextTick
回调,然后将所有的Promise
回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。
此外,process.nextTick
和Promise
回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住
。
这两个方法的回调到底谁快?
如下面的例子:
setImmediate(() => console.log(2)) setTimeout(() => console.log(1))
使用nodejs多次执行后,发现输出结果有时是1 2
,有时是2 1
。
对于多次执行输出结果不同,需要了解事件循环的基础问题。
首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):
setImmediate 向 Check 阶段 中添加了回调 console.log(2);setTimeout 向 Timer 阶段 中添加了回调 console.log(1)
这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。
为什么会有两种输出呢? 接下来一步很关键:
当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.
如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.
如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.
所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:
require('fs').readFile('my-file-path.txt', () => { setImmediate(() => console.log(2)) setTimeout(() => console.log(1)) });
这是因为消息循环在 Pneding I/O Phase
才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。
从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.
再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.
下面以一段代码来说明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') })
上面代码执行过程:
node初始化
执行JavaScript代码
遇到setTimeout
, 把回调函数放到Timer
队列中,记为settimeout1
遇到setTimeout
, 把回调函数放到Timer
队列中,记为settimeout2
遇到Promise
,执行,输出15,把回调函数放到微任务
队列,记为Promise1
遇到Promise
,执行,输出17,把回调函数放到微任务
队列,记为Promise2
代码执行结束,此阶段输出结果:15 17
没有process.nextTick
回调,略过
执行微任务
检查微任务队列是否有可执行回调,此时队列有2个回调:Promise1、Promise2
执行Promise1回调,输出16
执行Promise2回调,输出18
此阶段输出结果:16 18
进入第一次事件循环
进入Timer阶段
检查Timer队列是否有可执行的回调,此时队列有2个回调:settimeout1、settimeout2
执行settimeout1回调:
输出1、2、4
添加了2个微任务,记为Promise3、Promise4
添加了2个Timer任务,记为settimeout3、settimeout4
执行settimeout2回调,输出14
Timer队列任务执行完毕
没有process.nextTick
回调,略过
检查微任务队列是否有可执行回调,此时队列有2个回调:Promise3、Promise4
按顺序执行2个微任务,输出3、5
此阶段输出结果:1 2 4 14 3 5
Pending I/O Callback阶段没有任务,略过
进入 Poll 阶段
检查是否存在尚未完成的回调,此时有2个回调:settimeout3、settimeout4
執行settimeout3回呼
輸出6
新增了2個Timer任務,記為settimeout5、settimeout6
執行settimeout4回調,輸出13
#沒有process.nextTick
回調,略過
沒有微任務,略過
此階段輸出結果:6 13
check、closing階段沒有任務,略過
檢查是否還有活躍的handles(定時器、IO等事件句柄)
,有,繼續下一輪事件循環
進入第二次事件循環
進入Timer階段
檢查Timer隊列是否有可執行的回調,此時佇列有2個回呼:settimeout5、settimeout6
執行settimeout5回呼:
#輸出7、8、10
加入了2個微任務,記為Promise5、Promise6
執行settimeout6回調,輸出12
沒有process.nextTick
#此階段輸出結果:
7 8 10 12 9 11
檢查是否還有活躍的
handles(定時器、IO等事件句柄)
程式執行結束,輸出結果:
15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11
】
### ###以上是JavaScript在nodejs環境下執行機制和事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!