摘要:二事件循環與幀事件循環和上面個名詞的基本概念在此不再啰嗦了,我們著重看下它們之間的關系。瀏覽器是一個系統,所有的操作最終都會以頁面的形式展現,而頁面的基本單位是幀。當某一幀的任務占用大量時間的時候,會影響到下一幀的執行。
歡迎關注我的公眾號睿Talk,獲取我最新的文章:
Promise, setTimeout, requestAnimationFrame, requestIdleCallback 這幾個概念相信很多人都很熟悉了,最近在看 React Fiber 源碼的時候又對它們有了更深一層的認識,在此分享一下。下文將用 rAF 代表 requestAnimationFrame, rIC 代表 requestIdleCallback。
二、事件循環與幀事件循環和上面 4 個名詞的基本概念在此不再啰嗦了,我們著重看下它們之間的關系。瀏覽器是一個 UI 系統,所有的操作最終都會以頁面的形式展現,而頁面的基本單位是幀。一幀中可能包括的任務有下面幾種類型。
events: 點擊事件、鍵盤事件、滾動事件等
macro: 宏任務,如 setTimeout
micro: 微任務,如 Promise
rAF: requestAnimationFrame
Layout: CSS 計算,頁面布局
Paint: 頁面繪制
rIC: requestIdleCallback
理想情況下,頁面會以 60 幀每秒的幀率來運行,但實際上每秒繪制多少幀是由多個因素決定的,下面舉一些例子:
一個加載完成的靜態頁面,當用戶沒有進行交互的情況下,頁面不需要重繪,幀率為 0。
快速滾動頁面的時候,可視區域的內容不斷發生變化,瀏覽器會盡可能快的重繪頁面,理想幀率為 60。
假設頁面有一個注冊了回調的按鈕,回調執行需要 500 毫秒。當點擊按鈕后再快速滾動頁面,頭 500 毫秒頁面是卡住動不了的,后 500 毫秒會盡可能快的重繪頁面,這時候理想幀率為 30。
當使用 rAF 制作動畫的時候,瀏覽器會盡可能快的重繪頁面,桌面瀏覽器可能是 60 幀,移動瀏覽器可能是 30 幀。
從上面的例子可以看出,頁面的幀率不是固定的,是會動態變化的。當某一幀的任務占用大量時間的時候,會影響到下一幀的執行。那么誰來調節幀率呢?顯然只能依靠瀏覽器自身。作為開發者的我們是無法準確預知回調什么時候執行的。比如:
function animation() { console.log("time: ", +new Date()); setTimeout(animate, 1000 / 60); } animation();
上面的函數假定了瀏覽器以幀率 60 來運行,但當幀率達不到的時候,2 幀之間回調可能執行了多次,也可能一次都不執行,簡稱掉幀。
所以在制作動畫的時候,我們不能預設瀏覽器的幀率,正確的做法是通過 rAF 注冊回調, 由瀏覽器來控制動畫調用時機:
function animation() { console.log("time: ", +new Date()); requestAnimationFrame(animation); } animation();
rAF 會保證注冊的回調在下次渲染頁面之前執行,且只會執行一次。另外,當頁面處于不可見狀態時,rAF 會自動停止執行,以節省系統資源。
三、執行順序Promise, setTimeout , rAF 和 rIC 對應 4 種隊列:微任務隊列、宏任務隊列、animation 隊列和 idle 隊列。
微任務隊列會在 JS 運行棧為空的時候立即執行。
animation 隊列會在頁面渲染前執行。
宏任務隊列優先級低于微任務隊列,一般也會比 animation 隊列優先級低,但不是絕對 。
idle 隊列優先級最低,當瀏覽器有空閑時間的時候才會執行。
setTimeout(()=>console.log("setTimeout"), 0); Promise.resolve().then(()=>console.log("promise")); requestAnimationFrame(()=>console.log("animation")); requestIdleCallback(()=>console.log("idle")); // 執行結果大多數情況下是: promise, animation, setTimeout, idle // 少數情況是:promise, setTimeout, animation, idle
再來談談空閑時間怎么理解。假設在 1 秒內有 3 幀需要渲染:
第一幀,由于宏任務占用了大量的時間,沒有空閑時間。
第二幀,rAF占用的時間不多,有大量的空閑時間
第三幀,瀏覽器事件占用的時間不多,有大量的空閑時間
與rAF類似,rIC 的執行時機是由瀏覽器控制的,能更好的保證體驗,優化性能。一般優先級高的任務(如 UI 更新)會放在 rAF 隊列,優先級低的任務(如日志上傳)會放 rIC。
四、隊列特性在一個事件循環內,各個隊列有以下特性:
宏任務隊列,每次只會執行隊列內的一個任務。
微任務隊列,每次會執行隊列里的全部任務。假設微任務隊列內有 100 個 Promise,它們會一次過全部執行完。這種情況下極有可能會導致頁面卡頓。如果在微任務執行過程中繼續往微任務隊列中添加任務,新添加的任務也會在當前事件循環中執行,很容易造成死循環, 如:
function loop() { Promise.resolve().then(loop); } loop();
animation 隊列,跟微任務隊列有點相似,每次會執行隊列里的全部任務。但如果在執行過程中往隊列中添加新的任務,新的任務不會在當前事件循環中執行,而是在下次事件循環中執行。
idle 隊列,每次只會執行一個任務。任務完成后會檢查是否還有空閑時間,有的話會繼續執行下一個任務,沒有則等到下次有空閑時間再執行。需要注意的是此隊列中的任務也有可能阻塞頁面,當空閑時間用完后任務不會主動退出。如果任務占用時間較長,一般會將任務拆分成多個階段,執行完一個階段后檢查還有沒有空閑時間,有則繼續,無則注冊一個新的 idle 隊列任務,然后退出當前任務。React Fiber 就是用這個機制。但最新版的 React Fiber 已經不用 rIC 了,因為調用的頻率太低,改用 rAF 了
五、總結本文介紹了 4 種隊列的執行順序和每個隊列的特性,它們是:宏任務隊列、微任務隊列、animation 隊列和 idle 隊列。實際應用時可以根據它們各自的特點分配不同的任務。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/109452.html
摘要:比如下面一個例子例輸出為先輸出,沒有問題,因為是同步任務在主線程中優先執行,這里的問題是和任務的執行優先級是如何定義的。 在原文的基礎上加了一點參考資料 問題的引出 event loop都不陌生,是指主線程從任務隊列中循環讀取任務,比如 例1: setTimeout(function(){console.log(1)},0); console.log(2) //輸出2,1 在上述...
摘要:回調函數這是異步編程最基本的方法。對象對象是工作組提出的一種規范,目的是為異步編程提供統一接口。誕生后,出現了函數,它將異步編程帶入了一個全新的階段。 更多詳情點擊http://blog.zhangbing.club/Ja... Javascript 語言的執行環境是單線程的,如果沒有異步編程,根本沒法用,非卡死不可。 為了解決這個問題,Javascript語言將任務的執行模式分成兩種...
摘要:一般會這樣去寫要在第一個請求成功后才可以執行下一步這樣的寫法的原理是,當執行一些異步操作時,我們需要知道操作是否已經完成,所有當執行完成的時候會返回一個回調函數,表示操作已經完成。 前言 開篇首先設想一個日常開發常常會遇到的需求:在多個接口異步請求數據,然后利用這些數據來進行一系列的操作。一般會這樣去寫: $.ajax({ url: ......, success: f...
摘要:二瀏覽器端在講解事件循環之前先談談中同步代碼異步代碼的執行流程。三端我自己認為的事件循環和瀏覽器端還是有點區別的,它的事件循環依靠引擎。四總結本篇主要介紹了瀏覽器和對于事件循環機制實現,由于能力水平有限,其中可能有誤之處歡迎指出。 一、前言 前幾天聽公司一個公司三年的前端說今天又學到了一個知識點-微任務、宏任務,我問他這是什么東西,由于在吃飯他淺淺的說了下,當時沒太理解就私下學習整理一...
摘要:異步問題回調地獄首先,我們來看下異步編程中最常見的一種問題,便是回調地獄。同時使用也是異步編程最基礎和核心的一種解決思路。基于,目前也被廣泛運用,其是異步編程的一種解決方案,比傳統的回調函數解決方案更合理和強大。 關于 微信公眾號:前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實際編碼中,我們經常會遇到Javascript代碼異步執行的場景...
閱讀 1974·2023-04-25 15:45
閱讀 1214·2021-09-29 09:34
閱讀 2503·2021-09-03 10:30
閱讀 2009·2019-08-30 15:56
閱讀 1465·2019-08-29 15:31
閱讀 1273·2019-08-29 15:29
閱讀 3204·2019-08-29 11:24
閱讀 3061·2019-08-26 13:45