深入理解Javascript單執行緒談Event Loop

小周sri的碼農發表於2017-09-17

假如面試回答js的執行機制時,你可能說出這麼一段話:“Javascript的事件分同步任務和非同步任務,遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務佇列之中,等到執行棧執行完畢之後再去執行任務佇列之中的事件。但你能說出背後的原因嗎?

1.執行緒與程式

 

程式:是系統資源分配和排程的單元。一個執行著的程式就對應了一個程式。一個程式包括了執行中的程式和程式所使用到的記憶體和系統資源。

 

執行緒:執行緒是程式下的執行者,一個程式至少會開啟一個執行緒(主執行緒),也可以開啟多個執行緒。

 

2.同步和非同步

 

同步和非同步關注的是訊息結果通訊機制。

 

同步發出呼叫,在沒有得到結果前,該呼叫不返回。但是一旦呼叫返回,就得到返回值

 

非同步發出呼叫後,呼叫直接返回,沒有返回結果。但結果由回撥函式給出,至於什麼時候給出,不知道。(這個回撥函式肯定是在當前js執行完後才執行)

 

3.阻塞與非阻塞

 

阻塞和非阻塞關注的是程式在等待呼叫結果時的狀態.

阻塞呼叫呼叫結果返回之前,當前執行緒被掛起。呼叫執行緒只有在得到結果後才會返回。
非阻塞呼叫在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒。

4.為什麼JavaScript是單執行緒?

 

JavaScript單執行緒,程式按照順序排列,前面的必須處理好,後面的才會執行。JavaScript的設計初衷是作為瀏覽器指令碼語言,主要是簡單使用者互動、操作DOM等,所以這門語言要圍繞單執行緒來設計,否則出現複雜的同步問題。

 

5.Js的單執行緒與非同步矛盾嗎?

 

不矛盾!!!首先記住這句話:Js執行是單執行緒,但瀏覽器是多執行緒

  5.1:JS的單執行緒

 

    一個瀏覽器程式中只有一個JS的執行執行緒,同一時刻內只會有一段程式碼在執行。

  5.2:瀏覽器是多執行緒

 

    查閱資料,有些文章也說是模組,本文就以瀏覽器多執行緒來說,它常駐執行緒:

 

      渲染引擎執行緒:負責頁面的渲染。     

      JS引擎執行緒:負責JS的解析和執行(本文說的主執行緒就指js引擎執行緒)

 

      定時器觸發執行緒:處理定時事件,比如setTimeout, setInterval

 

      事件觸發執行緒:處理DOM事件

 

      非同步http請求執行緒:處理http請求

  

瀏覽器是Js使用場景,瀏覽器本身是典型的 GUI 工作執行緒GUI 工作執行緒在絕大多數系統中都實現為事件處理,避免阻塞互動)。故瀏覽器是事件驅動的(Event driven),瀏覽器中很多行為是非同步,會建立事件並放入任務佇列中。

 

由於Javascript 是單執行緒,它需要藉助非同步完成耗時或者時間不確定的操作,這些操作由瀏覽器的其它執行緒執行,這形成了非同步事件驅動。非同步事件驅動往往由瀏覽器的兩個或以上常駐執行緒共同完成的例如ajax非同步請求是由JS執行執行緒和非同步http請求執行緒事件觸發執行緒共同完成的

6.事件迴圈機制(Event Loop

  6.1:

 

    函式呼叫形成一個棧幀。

 

  function foo(b) { 
   let a = 10; 
   return a + b + 11; 
 }   
 function bar(x) { 
   let y = 3; 
   return foo(x * y); 
 }
 console.log(bar(7));

 

  當呼叫 bar 時,建立了第一個幀 ,幀中包含了 bar 的引數和區域性變數。

  當 bar 呼叫 foo 時,第二個幀就被建立,並被壓到第一個幀之上,幀中包含了 foo 的引數和區域性變數。

  當 foo 返回時,最上層的幀就被彈出棧(剩下 bar 函式的呼叫幀 )。

  當 bar 返回的時候,棧就空了。

  6.2:    

    物件被分配在一個堆中,一個用以表示一個記憶體中大的未被組織的區域。

 

    每一個執行緒只有一個棧,每一個程式只有一個堆。

  6.3:佇列   

    一個 JavaScript 執行時包含了一個待處理的訊息佇列。每一個訊息都與一個函式相關聯。

 

    當棧為空時,從佇列中取出一個訊息進行處理。這個處理過程包含了呼叫與這個訊息相關聯的函式。

 

    當棧再次為空的時候,也就意味著訊息處理結束。

 

 

 

7.任務佇列(訊息佇列)

 

  任務佇列是一個先進先出的資料結構,當主執行緒執行棧一清空,任務佇列的回撥函式就自動進入主執行緒。任務分成兩種

 

    1、同步任務在主執行緒上排隊執行的任務只有執行完前任務,才能執行後一個任務

 

    2、非同步任務:該任務不進入主執行緒、而進入任務佇列。當執行棧清空後,才去執行任務佇列中的任務。

8.非同步執行的執行機制

 

  由於JavaScript只能一次執行一段程式碼(由於其單執行緒性質),這些程式碼塊中的每一個都阻止其他非同步事件的進度。這意味著當非同步事件發生時(如滑鼠點選,定時器觸發或XMLHttpRequest完成),它將排隊等待稍後執行(這種排隊實際發生的確定會因瀏覽器到瀏覽器而異)。

 

  1、所有同步任務都在主執行緒上執行,形成一個執行棧

 

  2、當遇到非同步任務時(IO裝置操作等,就在任務佇列中新增一個事件,這個事件對應著該非同步任務的回撥函式。

 

  3、執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,進入執行棧,開始執行。

 

  4、主執行緒不斷重複第三步。這就形成了事件迴圈

 

結論:Javascript的事件分同步任務和非同步任務,遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務佇列之中,等到執行棧執行完畢之後再去執行任務佇列之中的事件。

9.事件和回撥函式的概念必要說明

 

  • 工作執行緒:是本文對除了js引擎執行緒之外的其它執行緒的統稱
  • 回撥函式:在一個函式中呼叫另外一個函式。這裡指非同步場景下為了非阻塞那些被主執行緒掛起來的程式碼。
  • 主執行緒讀取任務佇列,就是讀取裡面有哪些事件,執行對應的回撥函式。

 

工作執行緒完成一項任務,就任務佇列中新增一個事件。這裡的完成任務是指完成操作(clickmousetouchajax的資料完全請求回來......),並非指執行它的回撥函式

a.onclick = function () {
  console.log("roro")
}

如上段程式碼,僅是操作了click,但並沒有執行回撥函式列印roro

10.事件迴圈

  事件迴圈是:主執行緒重複從任務佇列中取訊息(事件),執行對應回撥函式的過程

 

上圖中,主執行緒執行的時候,產生堆(heap)和棧(stack),棧中的程式碼呼叫各種外部API,它們在任務佇列中加入各種事件(clickloaddone)。只要執行引擎棧棧中的程式碼執行完畢,主執行緒就會去讀取任務佇列,依次執行那些事件所對應的回撥函式。

11.定時器

 

setTimeout(function () {
  console.log('a');
}, 5000)

 

  Javascript執行引擎主執行緒執行的時候,產生堆和棧。程式中程式碼依次進入棧中等待執行,當呼叫setTimeout()方法時,瀏覽器的定時器執行緒下處理延時方法,當setTimeout方法執行5秒後到達觸發條件方法被新增到用於回撥的任務佇列

       執行引擎的執行棧為空,執行引擎開始輪詢檢查任務佇列是否有任務需要被執行,檢查到已經符合執行條件的延時方法,將延時方法console.log('a')入執行棧引擎發現呼叫了log()方法,於是又將log()方法入棧。然後對執行棧依次出棧執行,輸出‘a’,清空執行棧,整個執行完畢。

12.setTimeout(fn0)是立即執行嗎?

 

btn.onclick = function () {
  setTimeout(function () {
    console.log('a')
  }, 0);
}

 

setTimeout(fn,0)的含義是:指定某個任務在主執行緒的空閒時間,儘可能早執行。它被新增進任務佇列,因此要等到同步任務和任務佇列前一個事件都處理完,才執行。

13.ajax非同步請求是否真的非同步?

 

  1JS的執行執行緒(主執行緒)發起非同步請求瀏覽器會開一條新的HTTP請求執行緒來執行請求,繼續執行中剩下的任務,

 

  2、在新執行緒(HTTP請求執行緒)中,在執行請求的同時,瀏覽器會正常處理其他任務的執行。

 

  3、在未來的某一時刻當資料完全請求回來以後事件觸發執行緒監視到之前發起的HTTP請求已完成,會將指定的回撥函式放入任務佇列中。

 

  4、當瀏覽器執行棧空閒時,去掃描任務佇列中的回撥函式,依次壓入執行棧中處理。

 

所以:ajax請求是非同步由瀏覽器新開一個執行緒請求,事件回撥的時候放入Event loop任務佇列等候處理。詳細的例子,可以參考MDN文件對ajax的描述:同步和非同步

誤解:事件迴圈類似棧或佇列

  這裡順帶提一下:事件迴圈雖然涉及到類似佇列的結構,並不是採用棧的方式處理任務。事件迴圈作為一個程式被劃分為多個階段,每個階段處理一些特定任務,各階段輪詢排程。這些階段可以是定時器處理,dom事件處理,ajax非同步處理......

結語

JavaScript引擎只有一個執行緒,強制非同步事件排隊等待執行,Javascript語言的事件迴圈,是瀏覽器的處理和行為。另外,本文是我個人的學習筆記,通篇結合個人的理解,在某些地方表述不嚴謹,如有錯誤,希望指出。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相關文章