具有深度度:JavaScript 事件循環(huán)機制 (event loop)

本篇文章已經(jīng)默認你有了基礎(chǔ)的?ES6?和?javascript語法?知識。

本篇文章比較細致,如果已經(jīng)對同步異步,單線程等概念比較熟悉的讀者可以直接閱讀執(zhí)行棧后面的內(nèi)容了解 event loop 原理

在了解?JavaScript?事件循環(huán)機制之前,得先了解同步與異步的概念

同步與異步

同步(Sync

constcal =() =>{for(leti =0; i <1e8; i++) {// 做一些運算}}cal();console.log("finish");

同步的含義是如果一個事情沒有做完,則不能執(zhí)行下一個。

在這里的例子如果?cal?函數(shù)沒有執(zhí)行完畢?console.log?函數(shù)是不會執(zhí)行的

對于?cal?稱為 同步函數(shù)。

異步 (ASync)

$.ajax("xxx.com",function(res){// ...});console.log("finish");

在上述代碼中,$.ajax?的執(zhí)行是異步的,不會阻塞?console.log?的運行

即不必等到?$.ajax?請求返回數(shù)據(jù)后,才執(zhí)行?console.log

對于?$.ajax?稱為異步函數(shù)。

為什么要有異步函數(shù)?

單線程

javascript?是一門單線程語言,只能同時做一件事情。

如果沒有異步函數(shù),堵塞在程序的某個地方,會導(dǎo)致后面的函數(shù)得不到執(zhí)行,瀏覽器作為用戶交互界面,顯然要能及時反映用戶的交互,因此要有異步函數(shù)。

為什么?javascript?不采用多線程呢?專門派發(fā)一個線程去處理用戶交互他不好嗎?

這個你可能得去問?javascript?的作者了。

執(zhí)行棧

由于?javascript?是單線程語言,因此只有一個執(zhí)行棧(調(diào)用棧)

functionbaz(){console.log("exec")}functionbar(){? ? baz();}functionfoo(){? ? bar();}foo();

我們可以用一個動畫來演示執(zhí)行棧的調(diào)用過程

根據(jù)動畫流程,我們詳細說一下調(diào)用棧的情況

main?函數(shù),也就是把整個?javascript?看成一個函數(shù),入棧

foo?函數(shù)被執(zhí)行,入棧

bar?函數(shù)被執(zhí)行,入棧

baz?函數(shù)被執(zhí)行,入棧

console.log?函數(shù)被執(zhí)行,入棧

console.log?函數(shù)執(zhí)行完畢,出棧

baz?函數(shù)執(zhí)行完畢,出棧

bar?函數(shù)執(zhí)行完畢,出棧

foo?函數(shù)執(zhí)行完畢,出棧

main?函數(shù)執(zhí)行完畢,出棧

這種調(diào)用??梢栽诔绦驁箦e的時候起到很好的?debug?的作用

functionbaz(){thrownewError("noop!");}functionbar(){? ? baz();}functionfoo(){? ? bar();}foo();

在查看錯誤中,我們明顯的看到了之前提到的調(diào)用棧。

剛才的程序并無異步函數(shù),

如果我們在程序中用到了異步函數(shù)

console.log("begin");setTimeout(functioncb(){console.log("finish")},1000);

這個時候我們再看執(zhí)行棧

進棧出棧過程類似上面的分析,可是在這里,直到?main?函數(shù)執(zhí)行完了,我們都沒看到?cb?函數(shù)執(zhí)行,可是確確實實?1000ms?左右后?cb?函數(shù)真的執(zhí)行了,這里面是發(fā)生了什么情況?

在解釋這個之前,我們先引入兩個概念

宏觀任務(wù)和微觀任務(wù)

1. 宏觀任務(wù)

在?ES5?之前,異步操作由宿主發(fā)起,JavaScript?引擎并不能發(fā)起異步操作,這類的異步任務(wù)稱為宏觀任務(wù),比較典型的有

setTimeout(() =>{console.log("exec")},2000);

2.微觀任務(wù)

在?ES5?之后出現(xiàn)了?Promise?,用于解決回調(diào)地獄的問題,這個函數(shù)也是異步的,會等到?fulfill(resolve 或 reject)?后才會執(zhí)行?then?方法

newPromise((resolve, reject) =>{? ? resolve("hello world")}).then(data=>{console.log(data)})

這個異步任務(wù),由?v8?引擎發(fā)起 稱為微觀任務(wù)

這兩類任務(wù)對?event loop?也有影響

接下來進入本文章重點??!

event loop

event loop?分為瀏覽器環(huán)境和?node?環(huán)境,實現(xiàn)是不一樣的,本篇文章暫時只討論瀏覽器環(huán)境下的?event loop

1. 瀏覽器環(huán)境下的?event loop

接下來,我們具體看一個很大的例子

console.log("1");setTimeout(functioncb1(){console.log("2")},0);newPromise(function(resolve, reject){console.log("3")? ? resolve();}).then(functioncb2(){console.log("4");})console.log("5")

這段代碼用?event loop?的解釋是這樣的

用文字解釋如下,上述動畫以及文字解釋忽略?main?函數(shù)

console.log("1")?入棧出棧,控制臺顯示?1

setTimeout?入棧,加入異步任務(wù)隊列(此時處于等待執(zhí)行完成的狀態(tài),對于setTimeout來說就是等待延遲時間算執(zhí)行完成,對于Promise?來說就是被?fulfill?了才算執(zhí)行完成。

new Promise?入棧出棧,控制臺顯示?3,并且把函數(shù)放入異步隊列,等待完成了,就執(zhí)行?then?方法,這里的話,演示動畫忘記加上了。

console.log(5)?入棧出棧,控制臺顯示?5

至此,主函數(shù)內(nèi)的任務(wù)全部執(zhí)行完畢,

這里需要先知道,當(dāng)任務(wù)放入異步任務(wù)隊列后他們?nèi)绻瓿闪?,就會自動進入微觀任務(wù)或者宏觀任務(wù)隊列。

這個時候?event loop?檢索微觀任務(wù)隊列是否有任務(wù),如果有,就拖到 執(zhí)行棧中執(zhí)行,如果沒有的話,就檢索宏觀任務(wù)隊列是否有任務(wù)。

而且,如果一旦微觀任務(wù)隊列有任務(wù),就一定會先執(zhí)行微觀任務(wù)隊列的。

如果一旦執(zhí)行棧有任務(wù)就一定會先執(zhí)行執(zhí)行棧的。

可以用代碼表述如下

while(true) {while(如果執(zhí)行棧有任務(wù)) {// 執(zhí)行}if(微觀任務(wù)隊列有任務(wù)) {// 執(zhí)行continue;? ? }if(宏觀任務(wù)隊列有任務(wù)) {// 執(zhí)行continue;? ? }}

至此,我們很容易得到上面的代碼的執(zhí)行結(jié)果是

"1","3","5","4","2"

在做一個宏觀任務(wù)嵌套微觀任務(wù)的例子加深上述流程的理解。

console.log("1");setTimeout(() =>{console.log("2")newPromise(resolve=>{? ? ? resolve()? ? }).then(() =>{console.log("3")? ? })},0);setTimeout(() =>{console.log("4")},0);console.log("5")

執(zhí)行結(jié)果會是

"1","5","2","3","4"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容