1. 程式人生 > >JavaScript進階之執行機制

JavaScript進階之執行機制

onchange 正在執行 export 整體 查找 hist 全屏 而不是 加載圖片

不太正確的總結:

ES5

技術分享圖片

ES5事件輪詢較為簡單。

  1. 主線程執行棧在初次頁面後首先會渲染頁面;
  2. 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack);
  3. 異步任務有了運行結果,會通過在"事件隊列"之中註冊一個事件;
  4. 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列"。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行(setTimeOut事件還會檢查定時器);
  5. 主線程不停重復以上三個步驟。

ES6

ES6中事件輪詢有一些細微的差別(多了promise之後)。

技術分享圖片

註意點:

  • 宏任務按順序執行,且瀏覽器在每個宏任務之間渲染頁面
  • 所有微任務也按順序執行,且在以下場景會立即執行所有微任務

    • 每個回調之後且js執行棧中為空。
    • 每個宏任務結束後。

宏任務和微任務細節可以看這篇文章 Tasks, microtasks, queues and schedules,或者中文譯文JS事件循環機制(event loop)之宏任務、微任務。英文原文中有執行動畫,可以配合譯文閱讀。

以上總結不是很嚴謹。我查了一些資料想要弄清楚“宏任務”和“微任務”的概念是什麽時候出現的,然而並沒有找到。但是查找過程中發現了一些有趣的事情。

  1. 事件輪詢(Event Loop)是Html標準,而不是JavaScript標準;
  2. 關於事件輪詢詳細標準是在Html5中出現的;

event loop 的詳細處理模型就在html標準中,看完就明白了。https://www.w3.org/TR/2018/WD-html53-20181018/webappapis.html#event-loops。這裏做一個簡單翻譯。

譯文:

定義

為了協調事件,用戶交互,腳本,渲染,網絡等,用戶代理(user agents,可以理解為一個瀏覽器客戶端)必須使用該節定義的事件循環(event loops)。總共有兩種類型的event loops:瀏覽器上下文(browser context,理解為一個頁面,如標簽,窗口,包括frames,iframe)和workers。

一個用戶代理至少有一個瀏覽器上下文,一個瀏覽器上下文必須包含一個event loop。瀏覽器上下文結束,那麽event loop也跟著結束。

一個event loop有一個或多個任務隊列task queues,任務隊列是一系列排好序的任務組成,這些任務有:

  • Events(事件),常見的事件
  • Pasring(解析),html解析
  • Callbacks(回調),回調函數
  • Using a resource(使用某個資源),以非阻塞方式獲取資源(頁面加載圖片等)
  • Reacting to DOM manipulation(響應DOM操作),響應點擊等DOM操作

當用戶代理向任務隊列添加一個任務時,同一個任務源的任務必須被添加進同一個任務隊列中。比如一個用戶代理有兩個任務隊列,第一個任務隊列來處理鼠標和鍵盤事件(user interaction task source),第二個任務隊列來處理其他事件,那麽所有鼠標和鍵盤產生的事件就必須添加到第一個任務隊列中。

每一個任務隊列都有一個當前運行任務(currently running task),用來處理重入(reentrancy,函數重復調用),初始化為null。每個事件循環還有一個防止微任務重復調用的標誌(performing a microtask checkpoint flag,正在執行微任務標識),初始化為false。

處理模型(Processing model,重點

一個事件循環存在時必須不停地執行以下步驟:

  1. 從event loop的一個事件隊列中取出最先進入(原文oldest)的任務。如果有任務,則忽略以下操作。某些情況下,document還沒有完全激活,那麽用戶代理可能會選擇一個空的任務隊列,如果是這樣,跳轉步驟6。
  2. 將event loop的當前運行任務設置為上步驟選中的任務
  3. Run:執行選中的任務。
  4. 當前運行任務設為null。
  5. 將該任務從任務隊列中移除。
  6. Microtasks:執行微任務。詳細見下方。
  7. 更新渲染。如果這個event loop是一個瀏覽器上下文event loop,那麽執行以下子步驟:
    1. 更新Performance對象的now()方法的返回值為現在(now)
    2. 更新和當前事件循環相關Document對象列表,一般不需要排序,除了以下兩種情況:
      • Document B嵌套於Document A,那麽列表中B必須在A後面。
      • 如果A和B同時嵌套於C,那麽A和B在列表中順序必須滿足他們在C中樹順序(tree order)。
      以下的步驟必須根據列表中Document的順序重復執行 
    3. 如果一個頂層的瀏覽器上下文B在更新渲染中沒有變化,那麽會將他從document列表中移除。(操作沒有影響到他,就移除他,防止渲染性能影響,比如在iframe中操作,不會觸發父頁面的重新渲染)
    4. 如果一個嵌套的瀏覽器上下文B在更新渲染中沒有變化,那麽會將他從document列表中移除
    5. 對於列表中每一個完全激活的頁面,執行resize(執行resize相關事件)步驟,更新now時間
    6. 對於列表中每一個完全激活的頁面,執行scroll(執行scroll相關事件)步驟,更新now時間
    7. 對於列表中每一個完全激活的頁面,執行媒體查詢和修改(執行media query和onchange,change相關事件)步驟,更新now時間
    8. 對於列表中每一個完全激活的頁面,執行css動畫並發送事件步驟,更新now時間
    9. 對於列表中每一個完全激活的頁面,執行全屏渲染步驟,更新now時間
    10. 對於列表中每一個完全激活的頁面,執行動畫框架回調步驟,更新now時間
    11. 對於列表中每一個完全激活的頁面,更新渲染或者用戶交互
  8. 如果這是一個worker 事件循環,在事件循環中的任務隊列沒有任務而且WorkerGlobalScope 的關閉標識為true,那麽銷毀改事件循環,終止所有步驟,恢復到運行一個worker的步驟。
  9. 返回事件循環的第一步。

每個事件循環都有一個微任務隊列(microtask queue),一個微任務是一個一開始就放在微任務隊列的任務(不在任務隊列中)。總共有兩種微任務:單獨的回調微任務(solitary callback microtasks)復合微任務(compound microtasks)。

Microtasks處理(perform a microtask checkpoint):

  1. 正在執行微任務標識performing a microtask checkpoint flag)設為true
  2. 微任務隊列處理:如果微任務隊列為空,跳轉到 完成 節點
  3. 選擇 微任務隊列最先進入(原文oldest)的微任務
  4. 事件循環的當前執行任務設置為上一步選擇的微任務
  5. 執行:執行該微任務
  6. 這可能會涉及到腳本回調,可能會再次執行微任務處理(perform a microtask checkpoint),所以使用“正在執行微任務標識“來避免函數重入。
  7. 當前執行任務設為null
  8. 從微任務隊列中移除該任務,返回到微任務隊列處理步驟
  9. 完成:通知每一個受此事件循環的影響環境對象(environment settings object)他們的rejected promises
  10. 清除索引數據庫事務
  11. 正在執行微任務標識設為false

任務源

  • Dom操作任務源
  • 用戶交互任務源
  • 網絡任務源
  • 歷史返回操作任務源(調用history.back() 等類似api)

總結

  1. 整體運行過程和其他文章沒有太大差別,不過沒有宏任務隊列的概念,只有任務隊列和微任務隊列概念。
  2. 每執行完任務隊列中一個任務,或者任務隊列為空時,會立即執行微任務隊列中所有任務。
  3. 英文真差(還不如機翻)。。。

JavaScript進階之執行機制