切換語言為:簡體
事件迴圈詳解

事件迴圈詳解

  • 爱糖宝
  • 2024-06-15
  • 2079
  • 0
  • 0

我認為要理解事件迴圈,首先要理解什麼是程序,什麼是執行緒。

程序和執行緒

什麼是程序?

百科解釋: 程序(Process) 是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。 在當代面向執行緒設計的計算機結構中,程序是執行緒的容器。程式是指令、資料及其組織形式的描述,程序是程式的實體。是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程序是程式的實體。

說實話這種解釋我是看不懂。一番查閱後,找了一個很不錯的介紹:

程式執行需要有它自己專屬的記憶體空間,可以把這塊記憶體空間簡單的理解為程序

“專屬的記憶體空間”就可以理解為獨立。

事件迴圈詳解

可以透過瀏覽器f12,檢視工作管理員來看下開啟了哪些程序

事件迴圈詳解

每個應用至少有一個程序,程序之間相互獨立,即使要通訊,也需要雙方同意。

什麼是執行緒?

百科解釋: 執行緒(thread)  是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。

執行緒是最小的執行單元,而程序由至少一個執行緒組成。啟動一個程式的時候,作業系統會為該程式建立一塊記憶體,用來存放程式碼、執行中的資料和一個執行任務的主執行緒,我們把這樣的一個執行環境叫程序。說的在透徹一點,執行程式碼的「人」稱之為「執行緒」。

一個程序可以包含多個執行緒,但是一個程序至少要有一個執行緒,所以在程序開啟後會自動建立一個執行緒來執行程式碼,該執行緒稱之為主執行緒。

瀏覽器包含哪些程序和執行緒

  • 瀏覽器程序。主要負責介面顯示、使用者互動、子程序管理,同時提供儲存等功能

  • 渲染程序。核心任務是將 HTML、CSS 和 JavaScript 轉換為使用者可以與之互動的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是執行在該程序中,預設情況下,Chrome 會為每個 Tab 標籤建立一個渲染程序。出於安全考慮,渲染程序都是執行在沙箱模式下。

  • 網路程序。主要負責頁面的網路資源載入,之前是作為一個模組執行在瀏覽器程序裡面的,直至最近才獨立出來,成為一個單獨的程序。

我們這裏主要來看渲染程序,渲染程序啟動後,會開啟一個渲染主執行緒,主執行緒主要負責執行HTML、CSS、JS程式碼。

渲染主執行緒

每個渲染程序都有一個主執行緒,並且主執行緒非常繁忙,既要處理 DOM,又要計算樣式,還要處理佈局,同時還需要處理 JavaScript 任務以及各種輸入事件。那麼主執行緒如何排程任務呢?

排隊

將這些任務按照順序加入訊息佇列中,並且按照“先進先出”一個個執行。

事件迴圈詳解

我們來分析一下執行步驟:

  1. 渲染主執行緒會進入到一個無線迴圈中

  2. 如果新增一個任務,那麼則將新增的任務新增到訊息佇列的末尾

  3. 每一次迴圈都會檢查訊息佇列中是否有任務存在,如果存在,那麼渲染主執行緒會從訊息佇列的頭部中讀取任務,並執行任務,執行完畢後,則進入下一次迴圈;如果不存在,則主執行緒進入休眠狀態。

這裏新增任務並不是只有渲染執行緒的任務,其他所有執行緒(包括其他程序中的執行緒)可以隨時向訊息佇列中新增任務。在新增新任務的時候,如果主執行緒是處於休眠狀態,那麼則將主執行緒喚醒以繼續迴圈讀取並執行任務。

非同步

我們知道,所有的任務都是在單執行緒執行的,而且每次只能執行一個任務,那麼其他的任務就都處於一個等待的狀態。而有些任務在程式碼執行的過程中,是無法立即處理的,那麼這樣就會導致一個很嚴重的問題。如果瀏覽器主執行緒等待這些無法立即處理的任務的時機達到,就會導致主執行緒長期處於一個等待的狀態,也就是我們常說的"阻塞",就會導致瀏覽器卡死。

事件迴圈詳解

從圖中可以看到,當計時開始的時候,渲染主執行緒一直處於等待的狀態。

由於渲染主執行緒承擔著極其重要的工作,所以絕對不能阻塞,瀏覽器選擇了非同步來解決這個問題

事件迴圈詳解

使用非同步,當計時開始的時候,渲染主執行緒將任務交給計時執行緒去處理,隨後自身立即結束任務的執行,轉而執行後續程式碼,當計時執行緒完成時,將事先傳遞的回撥函式包裝成任務,加入到訊息佇列的末尾排隊,等待主執行緒排程執行。

在這種非同步模式下,瀏覽器永不阻塞,從而最大限度的保證了單執行緒的流暢執行。

優先順序

訊息佇列是“先進先出”的特性,那麼放入佇列中的任務,要等待前面的任務執行完畢之後,才能被執行。但是如果有的任務比較著急執行呢?比如我們常說的微任務比宏任務先執行。這個是因為任務有優先順序嗎?

其實並不是,任務沒有優先順序,任務就是需要排隊,在佇列中先進先出,但是訊息佇列有優先順序。

根據w3c最新解釋 html.spec.whatwg.org/multipage/w…

  • 每個任務都有一個任務型別,同一個型別的任務必須在一個佇列,不同型別的任務可以分屬於不同的佇列。 在一次事件迴圈中,瀏覽器可以根據實際情況從不同的佇列中取出任務執行。

  • 瀏覽器必須準備好一個微佇列,微佇列中的任務優先所有其他任務執行

和我們關係比較大的,在chrome瀏覽器中,至少要包含以下佇列:

  • 延時佇列 ——— 優先順序(中)

  • 互動佇列 ——— 優先順序(高)

  • 微佇列 ——— 優先順序(最高)

現代瀏覽器中,產生微任務有兩種方式。

  • 第一種方式是使用 MutationObserver 監控某個 DOM 節點,然後再透過 JavaScript 來修改這個節點,或者為這個節點新增、刪除部分子節點,當 DOM 節點發生變化時,就會產生 DOM 變化記錄的微任務。

  • 第二種方式是使用 Promise,當呼叫 Promise.resolve() 或者 Promise.reject() 的時候,也會產生微任務。

下面我們用一個題目來演示一下

Promise.resolve().then(() => {
  console.log('Promise1');
  setTimeout(() => {
    console.log('setTimeout2');
  }, 0);
});
setTimeout(() => {
  console.log('setTimeout1');
  Promise.resolve().then(() => {
    console.log('Promise2');
  });
}, 0);

碰到這種題目,首先立刻畫個圖

事件迴圈詳解 

首先渲染主執行緒開始執行js程式碼

事件迴圈詳解

  1. 當碰到promise.resolve.then(fn),立即把一個函式新增到微佇列, 這裏我們稱為Promise1任務

  2. 遇到setTimeout,然後交給計時執行緒計時,

  3. 在0s之後 將回調新增到延時佇列,這裏我們稱為setTimeout1任務 此時全域性js同步程式碼執行完畢。

而微佇列優先順序最高,則先執行Promise1任務,那麼則輸出Promise1,接著將遇到setTimeout,然後交給計時執行緒計時,在0s之後 將回調新增到延時佇列, 這裏我們稱為setTimeout2任務

事件迴圈詳解 

Promise1任務執行完畢之後,微佇列中已經沒有任務了,只有延時佇列有任務,那麼先執行setTimeout1任務。那麼先輸出setTimeout1,然後碰到promise.resolve.then(fn),立即把一個函式新增到微佇列, 這裏我們稱為Promise2任務。

事件迴圈詳解 

此時微佇列中有任務了,由於他的優先順序最高,那麼先執行Promise2任務,則輸出Promise2,Promise2任務執行完畢之後,微佇列中已經沒有任務了,只有延時佇列有任務,那麼先執行setTimeout2任務。那麼先輸出setTimeout2, 所以最後的結果為:Promise1、setTimeout1、Promise2、setTimeout2

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.