JavaScript可視化:事件循環

JavaScript是單線程(single-threaded): 同時只能做一個項目。通常這沒什麼大不了,但是想象一下,現在你正在運行一個需要30秒的任務。在任務期間,任何其它事情發生(JavaScript默認運行在瀏覽器的主線程上,因此整個UI是卡住的)之前都需要等待30秒。現在是2019年了,沒有人想要一個慢的、響應遲鈍的網頁。

幸運的是,瀏覽器給我們一些JavaScript引擎它自己沒有提供的特性:WeB API。它包括DOM API、setTimeout、HTTP請求等內容。這能幫助我們創建一些異步、非阻塞的行爲。

當我們調用一個函數的時候,它會被添加到一個叫做調用棧的東西中。調用棧不是瀏覽器特有的,而是JS引擎的一部分。它是棧意味着它是先進後出(想想一堆煎餅)。當函數返回一個值的時候,它會被彈出棧。
在這裏插入圖片描述
圖1:當函數被調用的時候會被推入到調用棧中,當函數返回一個值的時候會從調用棧中彈出

respond函數返回一個setTimeout函數。setTimeout通過Web API提供給我們,它讓我們在不阻塞主線程的情況下延遲執行任務。我們爲setTimeout傳遞的回調函數即箭頭函數() => { return ‘Hey’ }被添加到Web API。與此同時,setTimetout函數和respond函數被彈出棧,它們都返回了它們的值。
。
圖2:setTimeout通過瀏覽器提供,Web API將會注意我們傳入的回調

在Web API中,定時器運行時間和我們傳遞給setTimeout的第二個參數一樣長,即1000ms。回調函數不會立即被添加到調用棧中,而是會被傳給一個叫隊列的東西。
在這裏插入圖片描述
圖3:當定時器結束的時候(這個例子中是1000ms),回調會被傳遞到回調隊列

這是一個令人疑惑的部分:並不是說在1000ms後,回調函數被添加到調用棧(從而返回一個值)。它是在1000ms後被添加到隊列。由於這是一個隊列,因此函數必須等到輪到它的時候再執行。

這是我們一直在等待的部分,事件循環去做它唯一任務的時間到了:通過調用棧連接隊列!如果調用棧是空的,也就是說所有之前被調用的函數已經返回它們的值並且已經從棧內彈出,那麼隊列中的第一項會被添加到調用棧。在這個例子中,沒有其它的函數被調用,意味着到回調函數在隊列中是第一項時調用棧是空的。
在這裏插入圖片描述
圖4:事件循環會查看調用隊列和調用棧,如果調用棧是空的,它會將隊列中的第一項推入棧中。

回調函數被添加到調用棧調用然後返回一個值,最終被彈出棧。
在這裏插入圖片描述
圖5:回調函數被推入調用佔中執行。一但它返回一個值,就會從調用棧內彈出。

嘗試想出如果我們運行如下代碼控制檯會得到什麼?

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。
  5. 在baz返回內容後,事件循環看到調用棧是空的,然後將回調添加到調用棧中。 回調輸出Second
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章