揭秘: 一個 JavaScript 庫如何帶動 Chromium 的發(fā)展?
想要提高一個網(wǎng)頁的加載速度是非常困難的,如果你的網(wǎng)站是在使用 JavaScript 渲染的內(nèi)容,你必須要在網(wǎng)頁的加載速度和網(wǎng)頁的輸入響應(yīng)能力之間作出權(quán)衡:
一次性執(zhí)行首屏需要執(zhí)行的邏輯(負(fù)載性能好,輸入響應(yīng)能力差) 將復(fù)雜的邏輯拆分成更小塊的任務(wù)執(zhí)行,以保證對外界輸入的響應(yīng)(負(fù)載性能差,輸入響應(yīng)能力好)
為了避免這種取舍,Facebook 在 Chromium 中提出并實(shí)現(xiàn)了 isInputPending() API,它可以提高網(wǎng)頁的響應(yīng)能力,但是不會對性能造成太大影響。
目前 isInputPending API 僅在 Chromium 的 87 版本開始提供,其他瀏覽器并未實(shí)現(xiàn)。
背景
在現(xiàn)今的 JavaScript 生態(tài)中,大多數(shù)工作都是在一個線程完成的:主線程。這種設(shè)計為開發(fā)者提供了一個健壯的執(zhí)行模型,但是如果腳本執(zhí)行的時間太長,則用戶體驗(yàn)(尤其是響應(yīng)能力)可能會遭受嚴(yán)重?fù)p失。例如,用戶正在輸入一些內(nèi)容時, JavaScript 正在執(zhí)行大量的邏輯,則在這些邏輯完成之前,瀏覽器都不能處理用戶的輸入事件。
現(xiàn)在的最佳實(shí)踐是通過將復(fù)雜的邏輯拆分成更小塊的任務(wù)執(zhí)行來解決這種問題。在頁面加載期間,頁面可以運(yùn)行一些 JavaScript 邏輯,然后將控制權(quán)轉(zhuǎn)交給瀏覽器,這時瀏覽器可以檢測自己的事件隊列,看看是不是需要響應(yīng)用戶輸入,然后再繼續(xù)運(yùn)行 JavaScript ,這種方式雖然會有一些幫助,但是同時也可能會帶來其他問題。
每次頁面將控制權(quán)交還給瀏覽器時,瀏覽器都會花費(fèi)一些時間來檢查它的事件隊列,處理完事件后再獲取下一個 JavaScript 代碼邏輯。當(dāng)瀏覽器更快地響應(yīng)事件時,頁面的整體加載時間會變慢。而且,用戶輸交互比較多的情況下,頁面加載會非常慢。如果我們不那么頻繁地進(jìn)行上面的過程,那么瀏覽器響應(yīng)用戶事件所花費(fèi)的時間就會更長。

Facebook 提出的 isInputPending API 是第一個將中斷的概念用于瀏覽器用戶交互的的功能,并且允許 JavaScript 能夠檢查事件隊列而不會將控制權(quán)交于瀏覽器。

下面我們來具體看一個例子。
一個例子
假設(shè)您需要做很多顯示阻塞的工作來加載頁面,例如,從組件生成標(biāo)記,分解質(zhì)數(shù)或僅繪制一個很酷的加載微調(diào)器。這些中的每一個都分解為一個離散的工作項(xiàng)。使用調(diào)度程序模式,讓我們勾勒出如何在假設(shè)的processWorkQueue()函數(shù)中處理我們的工作:
假設(shè)你再首屏加載頁面時要處理非常多的阻塞邏輯,例如從組件生成標(biāo)記,分解質(zhì)數(shù),或者只是繪制一個很酷的加載器動畫。這些邏輯都會被分解成一個獨(dú)立的工作項(xiàng)。使用 scheduler 模式,讓我們在一個假設(shè)的 processWorkQueue() 函數(shù)中處理我們的邏輯:
const?DEADLINE?=?performance.now()?+?QUANTUM;
while?(workQueue.length?>?0)?{
??if?(performance.now()?>=?DEADLINE)?{
????//?Yield?the?event?loop?if?we're?out?of?time.
????setTimeout(processWorkQueue);
????return;
??}
??let?job?=?workQueue.shift();
??job.execute();
}
通過 processWorkQueue() 延時鏈?zhǔn)秸{(diào)用 setTimeout(),我們使瀏覽器能夠在某種程度上保持對輸入的響應(yīng),同時仍然在相對不間斷地運(yùn)行。但是,可能需要很長時間才能完成其他需要控制事件循環(huán)的工作,或者使 QUANTUM 事件延遲增加一毫秒。
在RAIL模型下,QUANTUM 一個好的值是<50ms,這取決于要完成的工作類型。該值主要決定了處理能力和延遲之間的平衡。
但是,我們還可以做的更好:
const?DEADLINE?=?performance.now()?+?QUANTUM;
while?(workQueue.length?>?0)?{
??if?(navigator.scheduling.isInputPending()?||?performance.now()?>=?DEADLINE)?{
????//?Yield?if?we?have?to?handle?an?input?event,?or?we're?out?of?time.
????setTimeout(processWorkQueue);
????return;
??}
??let?job?=?workQueue.shift();
??job.execute();
}
通過調(diào)用 navigator.scheduling.isInputPending(),我們可以更快地響應(yīng)輸入,同時仍然確保我們的阻塞邏輯能夠不間斷地執(zhí)行。如果在完成這些邏輯之前對用戶交互(例如繪畫)以外的其他操作不感興趣,則可以方便地增加輸入的長度 QUANTUM。
默認(rèn)情況下,“連續(xù)”的事件類型不會返回 isInputPending(),比如 mousemove,pointermove 等等。如果你對這些也感興趣的話,沒問題??梢酝ㄟ^為 isInputPending() 提供一個包含連續(xù)變量為true的字典:
const?DEADLINE?=?performance.now()?+?QUANTUM;
const?options?=?{?includeContinuous:?true?};
while?(workQueue.length?>?0)?{
??if?(navigator.scheduling.isInputPending(options)?||?performance.now()?>=?DEADLINE)?{
????//?Yield?if?we?have?to?handle?an?input?event?(any?of?them!),?or?we're?out?of?time.
????setTimeout(processWorkQueue);
????return;
??}
??let?job?=?workQueue.shift();
??job.execute();
}
像 React 這樣的框架正在將 isInputPending() 使用類似的邏輯構(gòu)建到其核心調(diào)度庫中。希望這將使使用這些框架的開發(fā)人員能夠從幕后的 isInputPending() 中受益,而不要進(jìn)行重大的重寫。
React Fiber
讓我們回想下 React Fiber 中的時間分片:
把一個耗時長的任務(wù)分成很多小片,每一個小片的運(yùn)行時間很短,雖然總時間依然很長,但是在每個小片執(zhí)行完之后,都給其他任務(wù)一個執(zhí)行的機(jī)會,這樣唯一的線程就不會被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會。
React Fiber 把更新過程碎片化,執(zhí)行過程如下面的圖所示,每執(zhí)行完一段更新過程,就把控制權(quán)交還給 React 負(fù)責(zé)任務(wù)協(xié)調(diào)的模塊,看看有沒有其他緊急任務(wù)要做,如果沒有就繼續(xù)去更新,如果有緊急任務(wù),那就去做緊急任務(wù)。

如果你對該庫感興趣,可以到
https://github.com/WICG/is-input-pending參與反饋和討論。
參考:https://web.dev/isinputpending/
不得不說 React 團(tuán)隊還是非常強(qiáng)的,一個 JavaScript 庫能帶動瀏覽器的發(fā)展。雖然這還是第一個由 Facebook 貢獻(xiàn)給瀏覽器的能力,不過未來可期,讓我們期待更多更強(qiáng)大的 API 吧!
你的點(diǎn)贊和在看是對我最大的支持 ??
