事件循環的概覽圖。
請注意上面這張圖,事件循環的那些事,將在這個圖上緩緩展開。
事件循環(Event Loop),怎么說呢,每個JAVAscript開發者都必須要經過它的蹂躪。初次見面,你會覺得它令人迷惑難以理解。我是一個視覺型學習者,所以我嘗試用視覺的方式來搞定它,沒錯,視覺的方式,就是那種低像素的gif圖片 - 現在是9012年了,gif還走在像素化的世界里用格子畫馬里奧。
那么,什么是事件循環,我們又為什么要關注它呢?
JavaScript是以單線程的方式運行的:同一時間只能做一件事。通常情況下,這也沒啥大不了的,但是假設現在要運行一個任務,這個任務要跑30秒......在任務運行的這30秒內,我們什么都干不了(JavaScript默認和UI渲染占用瀏覽器的主線程,當JavaScript在愜意的奔跑30秒時,UI渲染只能苦苦等待而被阻斷,也就是我們見到的頁面卡頓)。現在都什么年代了,一個卡頓、點擊沒響應的網站是吸引不了用戶的。
幸運的是,瀏覽器給我們打開了一扇窗,它提供了一些JavaScript引擎本身沒有的特性:Web API,包括`DOM API`,`setTimeout`,`HTTP requests`等等。這些可以幫助我們來創建異步的,不會被阻斷的行為。
當我們調用一個函數時,函數會被壓入堆棧。堆棧不是瀏覽器特有的東西,它是JavaScript引擎的一部分。堆棧,顧名思義就是一個棧,遵循著先進后出的原則(想象一下,一疊烙餅是怎么來的,又是怎么沒的)。當這個函數返回時,它會被從棧里拋出去。
1、函數執行時,壓入堆棧,執行完拋出堆棧。
上圖的`respond`函數返回了一個`setTimeout`函數,`setTimeout`由`Web API`提供:它可以讓任務延遲執行,而且不會阻礙主線程。箭頭函數`() => { return 'Hey' }`作為回調函數,傳遞給`setTimeout`,然后會被添加到`Web API`中。同時,`setTimemout`和`respond`函數都被拋出了堆棧,而且他們都返回了自己的值。
2、瀏覽器提供setTimeout,Web API負責回調函數。
在`Web API`中,當第二個參數,`1000毫秒`被傳遞過去時,一個計時器就開始運行。此時這個回調函數不會馬上被添加到堆棧中執行,它會被先添加到一個隊列中。
3、計時器完成時,添加回調函數到回調函數隊列。
這里是讓人容易迷惑的地方:1000毫秒后,回調函數并不會被添加到堆棧,它只是被簡單的添加到了一個隊列中,然后,等待著被叫號。
現在到了我們期待已久的時刻,事件循環要開始它唯一的使命:連接隊列和堆棧。如果堆棧是空的,所有以前被調用的函數已經返回并且從堆棧中拋出,那么把隊列中的第一個添加到堆棧中。此時沒有別的函數被調用,意味著當回調函數成為隊列中的第一個時,堆棧是空的。
4、事件循環發現堆棧為空,添加隊列第一項到堆棧中。
回調函數被添加到堆棧后,執行,然后返回,最后被拋出堆棧。
5、回調函數添加到堆棧,執行,返回,最終拋出堆棧。
紙上得來終覺淺,絕知此事要躬行??纯匆韵麓a最終會輸出什么:
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
理解了沒?讓我們來快速看一下,這段代碼在瀏覽器中運行時發生了什么:
執行流程
1. 調用`bar`,`bar`返回了一個`setTimeout`函數。
2. 傳遞給`setTimeout`的回調函數被添加到`Web API`中,同時`setTimeout`函數和`bar`返回并被拋出堆棧。
3. 計時器開始運行,同時`foo`被調用,輸出`First`,`foo`返回(`undefined`)。`baz`被調用,回調函數被添加到隊列中。
4. `baz`輸出`Third`,`baz`返回,`baz`被拋出堆棧,事件循環發現堆棧是空的,就把回調函數添加到堆棧中。
5. 回調函數輸出`Second`。
好了,希望現在你已經開始體會到事件循環的美妙。如果還有疑惑,不要擔心,最重要的是理解錯誤發生、行為產生背后的根源,這樣可以有效定位問題、快速搜索并最終在`StackOverflow`中找到真解。如果有任何問題,請隨時聯系我。
翻譯來源:https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif