摘要:如果執(zhí)行的準(zhǔn)備時間大于了,因?yàn)閳?zhí)行同步代碼后,定時器的回調(diào)已經(jīng)被放入隊(duì)列,所以會先執(zhí)行隊(duì)列。
JavaScript 是一門單線程語言,之所以說是單線程,是因?yàn)樵跒g覽器中,如果是多線程,并且兩個線程同時操作了同一個 Dom 元素,那最后的結(jié)果會出現(xiàn)問題。所以,JavaScript 是單線程的,但是如果完全由上至下的一行一行執(zhí)行代碼,假如一個代碼塊執(zhí)行了很長的時間,后面必須要等待當(dāng)前執(zhí)行完畢,這樣的效率是非常低的,所以有了異步的概念,確切的說,JavaScript 的主線程是單線程的,但是也有其他的線程去幫我們實(shí)現(xiàn)異步操作,比如定時器線程、事件線程、Ajax 線程。
在瀏覽器中執(zhí)行 JavaScript 有兩個區(qū)域,一個是我們平時所說的同步代碼執(zhí)行,是在棧中執(zhí)行,原則是先進(jìn)后出,而在執(zhí)行異步代碼的時候分為兩個隊(duì)列,macro-task(宏任務(wù))和 micro-task(微任務(wù)),遵循先進(jìn)先出的原則。
// 作用域鏈 function one() { console.log(1); function two() { console.log(2); function three() { console.log(3); } three(); } two(); } one(); // 1 // 2 // 3
上面的代碼都是同步的代碼,在執(zhí)行的時候先將全局作用域放入棧中,執(zhí)行全局作用域中的代碼,解析了函數(shù) one,當(dāng)執(zhí)行函數(shù)調(diào)用 one() 的時候?qū)?one 的作用域放入棧中,執(zhí)行 one 中的代碼,打印了 1,解析了 two,執(zhí)行 two(),將 two 放入棧中,執(zhí)行 two,打印了 2,解析了 three,執(zhí)行了 three(),將 three 放入棧中,執(zhí)行 three,打印了 3。
在函數(shù)執(zhí)行完釋放的過程中,因?yàn)槿肿饔糜蛑杏?one 正在執(zhí)行,one 中有 two 正在執(zhí)行,two 中有 three 正在執(zhí)行,所以釋放內(nèi)存時必須由內(nèi)層向外層釋放,three 執(zhí)行后釋放,此時 three 不再占用 two 的執(zhí)行環(huán)境,將 two 釋放,two 不再占用 one 的執(zhí)行環(huán)境,將 one 釋放,one 不再占用全局作用域的執(zhí)行環(huán)境,最后釋放全局作用域,這就是在棧中執(zhí)行同步代碼時的先進(jìn)后出原則,更像是一個杯子,先放進(jìn)去的在最下面,需要最后取出。
而異步隊(duì)列更像時一個管道,有兩個口,從入口進(jìn),從出口出,所以是先進(jìn)先出,在宏任務(wù)隊(duì)列中代表的有 setTimeout、setInterval、setImmediate、MessageChannel,微任務(wù)的代表為 Promise 的 then 方法、MutationObserve(已廢棄)。
案例 1
let messageChannel = new MessageChannel(); let prot2 = messageChannel.port2; messageChannel.port1.postMessage("I love you"); console.log(1); prot2.onmessage = function(e) { console.log(e.data); }; console.log(2); // 1 // 2 // I love you
從上面案例中可以看出,MessageChannel 是宏任務(wù),晚于同步代碼執(zhí)行。
案例 2
setTimeout(() => console.log(1), 2000); setTimeout(() => console.log(2), 1000); console.log(3); // 3 // 2 // 1
上面代碼可以看出其實(shí) setTimeout 并不是在同步代碼執(zhí)行的時候就放入了異步隊(duì)列,而是等待時間到達(dá)時才會放入異步隊(duì)列,所以才會有了上面的結(jié)果。
案例 3
setImmediate(function() { console.log("setImmediate"); }); setTimeout(function() { console.log("setTimeout"); }, 0); console.log(1); // 1 // setTimeout // setImmediate
同為宏任務(wù),setImmediate 在 setTimeout 延遲時間為 0 時是晚于 setTimeout 被放入異步隊(duì)列的,這里需要注意的是 setImmediate 在瀏覽器端,到目前為止只有 IE 實(shí)現(xiàn)了。
上面的案例都是關(guān)于宏任務(wù),下面我們舉一個有微任務(wù)的案例來看一看微任務(wù)和宏任務(wù)的執(zhí)行機(jī)制,在瀏覽器端微任務(wù)的代表其實(shí)就是 Promise 的 then 方法。
案例 4
setTimeout(() => { console.log("setTimeout1"); Promise.resolve().then(data => { console.log("Promise1"); }); }, 0); Promise.resolve().then(data => { console.log("Promise2"); setTimeout(() => { console.log("setTimeout2"); }, 0); }); // Promise2 // setTimeout1 // Promise1 // setTimeout2
從上面的執(zhí)行結(jié)果其實(shí)可以看出,同步代碼在棧中執(zhí)行完畢后會先去執(zhí)行微任務(wù)隊(duì)列,將微任務(wù)隊(duì)列執(zhí)行完畢后,會去執(zhí)行宏任務(wù)隊(duì)列,宏任務(wù)隊(duì)列執(zhí)行一個宏任務(wù)以后,會去看看有沒有產(chǎn)生新的微任務(wù),如果有則清空微任務(wù)隊(duì)列后再執(zhí)行下一個宏任務(wù),依次輪詢,直到清空整個異步隊(duì)列。
在 Node 中的事件輪詢機(jī)制與瀏覽器相似又不同,相似的是,同樣先在棧中執(zhí)行同步代碼,同樣是先進(jìn)后出,不同的是 Node 有自己的多個處理不同問題的階段和對應(yīng)的隊(duì)列,也有自己內(nèi)部實(shí)現(xiàn)的微任務(wù) process.nextTick,Node 的整個事件輪詢機(jī)制是 Libuv 庫實(shí)現(xiàn)的。
Node 中事件輪詢的流程如下圖:
從圖中可以看出,在 Node 中有多個隊(duì)列,分別執(zhí)行不同的操作,而每次在隊(duì)列切換的時候都去執(zhí)行一次微任務(wù)隊(duì)列,反復(fù)的輪詢。
案例 1
setTimeout(function() { console.log("setTimeout"); }, 0); setImmediate(function() { console.log("setInmediate"); });
默認(rèn)情況下 setTimeout 和 setImmediate 是不知道哪一個先執(zhí)行的,順序不固定,Node 執(zhí)行的時候有準(zhǔn)備的時間,setTimeout 延遲時間設(shè)置為 0 其實(shí)是大概 4ms,假設(shè) Node 準(zhǔn)備時間在 4ms 之內(nèi),開始執(zhí)行輪詢,定時器沒到時間,所以輪詢到下一隊(duì)列,此時要等再次循環(huán)到 timer 隊(duì)列后執(zhí)行定時器,所以會先執(zhí)行 check 隊(duì)列的 setImmediate。
如果 Node 執(zhí)行的準(zhǔn)備時間大于了 4ms,因?yàn)閳?zhí)行同步代碼后,定時器的回調(diào)已經(jīng)被放入 timer 隊(duì)列,所以會先執(zhí)行 timer 隊(duì)列。
案例 2
setTimeout(() => { console.log("setTimeout1"); Promise.resolve().then(() => { console.log("Promise1"); }); }, 0); setTimeout(() => { console.log("setTimeout2"); }, 0); console.log(1); // 1 // setTimeout1 // setTimeout2 // Promise1
Node 事件輪詢中,輪詢到每一個隊(duì)列時,都會將當(dāng)前隊(duì)列任務(wù)清空后,在切換下一隊(duì)列之前清空一次微任務(wù)隊(duì)列,這是與瀏覽器端不一樣的。
瀏覽器端會在宏任務(wù)隊(duì)列當(dāng)中執(zhí)行一個任務(wù)后插入執(zhí)行微任務(wù)隊(duì)列,清空微任務(wù)隊(duì)列后,再回到宏任務(wù)隊(duì)列執(zhí)行下一個宏任務(wù)。
上面案例在 Node 事件輪詢中,會將 timer 隊(duì)列清空后,在輪詢下一個隊(duì)列之前執(zhí)行微任務(wù)隊(duì)列。
案例 3
setTimeout(() => { console.log("setTimeout1"); }, 0); setTimeout(() => { console.log("setTimeout2"); }, 0); Promise.resolve().then(() => { console.log("Promise1"); }); console.log(1); // 1 // Promise1 // setTimeout1 // setTimeout2
上面代碼的執(zhí)行過程是,先執(zhí)行棧,棧執(zhí)行時打印 1,Promise.resolve() 產(chǎn)生微任務(wù),棧執(zhí)行完畢,從棧切換到 timer 隊(duì)列之前,執(zhí)行微任務(wù)隊(duì)列,再去執(zhí)行 timer 隊(duì)列。
案例 4
setImmediate(() => { console.log("setImmediate1"); setTimeout(() => { console.log("setTimeout1"); }, 0); }); setTimeout(() => { console.log("setTimeout2"); setImmediate(() => { console.log("setImmediate2"); }); }, 0); //結(jié)果1 // setImmediate1 // setTimeout2 // setTimeout1 // setImmediate2 // 結(jié)果2 // setTimeout2 // setImmediate1 // setImmediate2 // setTimeout1
setImmediate 和 setTimeout 執(zhí)行順序不固定,假設(shè) check 隊(duì)列先執(zhí)行,會執(zhí)行 setImmediate 打印 setImmediate1,將遇到的定時器放入 timer 隊(duì)列,輪詢到 timer 隊(duì)列,因?yàn)樵跅V袌?zhí)行同步代碼已經(jīng)在 timer 隊(duì)列放入了一個定時器,所以按先后順序執(zhí)行兩個 setTimeout,執(zhí)行第一個定時器打印 setTimeout2,將遇到的 setImmediate 放入 check 隊(duì)列,執(zhí)行第二個定時器打印 setTimeout1,再次輪詢到 check 隊(duì)列執(zhí)行新加入的 setImmediate,打印 setImmediate2,產(chǎn)生結(jié)果 1。
假設(shè) timer 隊(duì)列先執(zhí)行,會執(zhí)行 setTimeout 打印 setTimeout2,將遇到的 setImmediate 放入 check 隊(duì)列,輪詢到 check 隊(duì)列,因?yàn)樵跅V袌?zhí)行同步代碼已經(jīng)在 check 隊(duì)列放入了一個 setImmediate,所以按先后順序執(zhí)行兩個 setImmediate,執(zhí)行第一個 setImmediate 打印 setImmediate1,將遇到的 setTimeout 放入 timer 隊(duì)列,執(zhí)行第二個 setImmediate 打印 setImmediate2,再次輪詢到 timer 隊(duì)列執(zhí)行新加入的 setTimeout,打印 setTimeout1,產(chǎn)生結(jié)果 2。
案例 5
setImmediate(() => { console.log("setImmediate1"); setTimeout(() => { console.log("setTimeout1"); }, 0); }); setTimeout(() => { process.nextTick(() => console.log("nextTick")); console.log("setTimeout2"); setImmediate(() => { console.log("setImmediate2"); }); }, 0); //結(jié)果1 // setImmediate1 // setTimeout2 // setTimeout1 // nextTick // setImmediate2 // 結(jié)果2 // setTimeout2 // nextTick // setImmediate1 // setImmediate2 // setTimeout1
這與上面一個案例類似,不同的是在 setTimeout 執(zhí)行的時候產(chǎn)生了一個微任務(wù) nextTick,我們只要知道,在 Node 事件輪詢中,在切換隊(duì)列時要先去執(zhí)行微任務(wù)隊(duì)列,無論是 check 隊(duì)列先執(zhí)行,還是 timer 隊(duì)列先執(zhí)行,都會很容易分析出上面的兩個結(jié)果。
案例 6
const fs = require("fs"); fs.readFile("./.gitignore", "utf8", function() { setTimeout(() => { console.log("timeout"); }, 0); setImmediate(function() { console.log("setImmediate"); }); }); // setImmediate // timeout
上面案例的 setTimeout 和 setImmediate 的執(zhí)行順序是固定的,前面都是不固定的,這是為什么?
因?yàn)榍懊娴牟还潭ㄊ窃跅V袌?zhí)行同步代碼時就遇到了 setTimeout 和 setImmediate,因?yàn)闊o法判斷 Node 的準(zhǔn)備時間,不確定準(zhǔn)備結(jié)束定時器是否到時并加入 timer 隊(duì)列。
而上面代碼明顯可以看出 Node 準(zhǔn)備結(jié)束后會直接執(zhí)行 poll 隊(duì)列進(jìn)行文件的讀取,在回調(diào)中將 setTimeout 和 setImmediate 分別加入 timer 隊(duì)列和 check 隊(duì)列,Node 隊(duì)列的輪詢是有順序的,在 poll 隊(duì)列后應(yīng)該先切換到 check 隊(duì)列,然后再重新輪詢到 timer 隊(duì)列,所以得到上面的結(jié)果。
案例 7
Promise.resolve().then(() => console.log("Promise")); process.nextTick(() => console.log("nextTick")); // nextTick // Promise
在 Node 中有兩個微任務(wù),Promise 的 then 方法和 process.nextTick,從上面案例的結(jié)果我們可以看出,在微任務(wù)隊(duì)列中 process.nextTick 是優(yōu)先執(zhí)行的。
上面內(nèi)容就是瀏覽器與 Node 在事件輪詢的規(guī)則,相信在讀完以后應(yīng)該已經(jīng)徹底弄清了瀏覽器的事件輪詢機(jī)制和 Node 的事件輪詢機(jī)制,并深刻的體會到了他們之間的相同和不同。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98286.html
摘要:回調(diào)函數(shù)任務(wù)完成的時候,需要執(zhí)行哪段代碼來處理呢當(dāng)然是回調(diào)函數(shù)了。事件處理器和回調(diào)函數(shù)類似。但是特定的事件處理器在瀏覽器進(jìn)入異步事件驅(qū)動階段時就會針對特定的事件注冊。當(dāng)事件對象返回到執(zhí)行線程時,事件處理器也會同時進(jìn)入執(zhí)行棧中執(zhí)行。 Event Loop(事件輪詢)機(jī)制是一個經(jīng)常把人搞暈的東東。我不敢說我完全明白,只是在此談?wù)勎业臏\見。 事件的處理 瀏覽器是一個事件驅(qū)動(event-dr...
摘要:基本原理函數(shù)監(jiān)視的文件描述符分類,分別是和。具體模型如下與模式相比,在完成數(shù)據(jù)讀取之后,將業(yè)務(wù)處理過程交由一個線程池來完成,主線程直接返回進(jìn)行下一次循環(huán)操作,效率大大提升。是提供的最高效的網(wǎng)絡(luò)模型。 I/O多路復(fù)用 IO多路復(fù)用就是通過一種機(jī)制,一個進(jìn)程可以監(jiān)聽多個文件描述符,一個某個描述符就緒(一般是讀就緒或?qū)懢途w),就能夠通知程序進(jìn)行相應(yīng)的讀寫操作。select、poll、epol...
摘要:之所以是單線程,取決于它的實(shí)際使用,例如不可能同添加一個和刪除這個,所以它只能是單線程的。所以,這個新標(biāo)準(zhǔn)并沒有改變單線程的本質(zhì)。 原文博客地址:https://finget.github.io/2018/05/21/async/ 異步 什么是單線程,和異步有什么關(guān)系 什么是event-loop 是否用過jQuery的Deferred Promise的基本使用和原理 介紹一下asyn...
摘要:阻塞請求結(jié)果返回之前,當(dāng)前線程被掛起。也就是說在異步中,不會對用戶線程產(chǎn)生任何阻塞。當(dāng)前線程在拿到此次請求結(jié)果的過程中,可以做其它事情。事實(shí)上,可以只用一個線程處理所有的通道。 準(zhǔn)備知識 同步、異步、阻塞、非阻塞 同步和異步說的是服務(wù)端消息的通知機(jī)制,阻塞和非阻塞說的是客戶端線程的狀態(tài)。已客戶端一次網(wǎng)絡(luò)請求為例做簡單說明: 同步同步是指一次請求沒有得到結(jié)果之前就不返回。 異步請求不會...
閱讀 1125·2021-11-24 09:38
閱讀 3229·2021-11-19 09:56
閱讀 2955·2021-11-18 10:02
閱讀 721·2019-08-29 12:50
閱讀 2566·2019-08-28 18:30
閱讀 859·2019-08-28 18:10
閱讀 3659·2019-08-26 11:36
閱讀 2640·2019-08-23 18:23