前言
- 本篇梳理下fiber架構的基礎知識。
requestIdleCallback
-
這玩意只在瀏覽器中有,一般情況,瀏覽器在解析js時,如果任務太耗時,就會阻塞用戶操作,界面成卡死狀態。就像我前面無聊寫的那個對賬工具,計算量太大頁面完全定住沒法動。
-
而 requestIdleCallback,雖然不能解決js卡死問題,但如果將大的js任務拆解成一個個小的js任務,然後通過這個api進行分段調用,即可讓用戶覺得好像不卡一樣。
-
這個api有2個參數,一個就是需要調用的函數,一個是超時設置,mdn介紹。
-
就是當瀏覽器空閒了,就會調用此函數,如果設置了deadline時間,那麼不管瀏覽器是不是在忙,都得調用這個函數。當然瀏覽器在忙的時候是無法插入此函數的,只能走到空閒時間片時強制執行此函數。沒看懂我這句的看mdn。
-
這個callback函數可以接收到一個deadline,裏面有2個方法。deadlinemdn
-
一個是IdleDeadline.timeRemaining(),表示當前閒置週期的預估剩餘毫秒數。
-
還有個IdleDeadline.didTimeout,表示是否超時,布爾值。
-
看個例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>yehuozhili</title>
</head>
<body>
<div id="root"></div>
</body>
<script>
function sleep(delay) {
for (let start = Date.now(); Date.now() - start <= delay;) { }
}
let works = [
() => {
console.log('11111')
sleep(20)
console.log('over1')
},
() => {
console.log('222')
// sleep(20)
console.log('over2')
},
() => {
console.log('3333')
// sleep(29)
console.log('over3')
}
]
function callback(deadline) {
console.log(deadline.timeRemaining(), deadline.didTimeout)
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0) {
performUnitOfWork()
}
//走到這表示不滿足while條件,下一次時間不夠了,進行遞歸調用
if (works.length > 0) {//當works爲空就出去了
window.requestIdleCallback(callback, { timeout: 1000 })
}
}
function performUnitOfWork() {
works.shift()()
}
window.requestIdleCallback(callback, { timeout: 1000 })
</script>
</html>
- 可以發現最後輸出:
index.html:75 14.42 false
index.html:59 11111
index.html:61 over1
index.html:75 9.28 false
index.html:64 222
index.html:66 over2
index.html:69 3333
index.html:71 over3
- 我們第一個任務的sleep(20)的時間超過了1幀所剩餘的14.42ms,所以當執行完畢後,dealine.timeRemaining()會等於0,這樣時間不夠執行下一個任務,所以進入下面的遞歸idle調用判斷。
- 這樣遞歸idle,就可以把執行權交還給瀏覽器,使得瀏覽器看起來沒死機的樣子,然後當瀏覽器空閒時,進行遞歸完成下一個任務。
- 由於任務2和3我都把sleep註釋掉了,所以任務2執行完,deadline.timeRemaining剩餘時間大於0,所以夠執行下一個任務,繼續在while循環裏運行下一個任務。因爲在while裏連續執行而不是遞歸調用,所以中間沒有再次打印console.log那段。
- React的fiber就是基於這種思想,把原本diff的不可中斷遞歸操作,拆解成多個小任務進行遞歸調用,使得diff的過程不會卡住瀏覽器。雖然計算量不變,但用戶體驗更好了。
MessageChannel
- 我們知道只有谷歌瀏覽器支持idle,react肯定不能搞得只能給谷歌瀏覽器用,所以兼容有使用messageChannel來製作相同功能。
- mdn地址
- 這玩意其實就是個宏任務,vue的nextTick以前是這玩意搞得,還看見有人用來搞深拷貝的騷操作。
- 一般瀏覽器都是會清空微任務,然後從宏任務隊列裏一個一個拿出來執行宏任務,如果有碰到微任務立馬執行。而瀏覽器渲染是在宏任務執行之前。
- 所以,宏任務變相等於idle,不會阻塞渲染。
- 看個hack例子:
function sleep(delay) {
for (let start = Date.now(); Date.now() - start <= delay;) { }
}
let root = document.getElementById('root')
let works = [
() => {
console.log('11111')
root.textContent = '111'
sleep(20)
console.log('over1')
},
() => {
console.log('222')
// sleep(20)
root.textContent = '2222'
console.log('over2')
},
() => {
console.log('3333')
root.textContent = '3333'
sleep(29)
console.log('over3')
},
() => {
console.log('444')
root.textContent = '4444'
console.log('over4')
}
]
function callback(deadline) {
console.log(deadline.timeRemaining(), deadline.didTimeout)
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0) {
performUnitOfWork()
}
//走到這表示不滿足while條件,下一次時間不夠了,進行遞歸調用
if (works.length > 0) {//當works爲空就出去了
// window.requestIdleCallback(callback, { timeout: 1000 })
window.myIdle(callback, { timeout: 1000 })
}
}
function performUnitOfWork() {
works.shift()()
}
//window.requestIdleCallback(callback, { timeout: 1000 })
let channel = new MessageChannel()
let perFrameTime = (1000 / 60);
let timeRemaining = () => perFrameTime - (Date.now() - startTime);//1幀減去當前減開始
let pendingCallback;
let timeoutTime, startTime
channel.port2.onmessage = () => {//接收消息
if (pendingCallback) {
pendingCallback({ didTimeout: Date.now() > timeoutTime, timeRemaining });
}
}
window.myIdle = (callback, options) => {
timeoutTime = Date.now() + options.timeout;//現在時間戳+傳入
startTime = Date.now();
pendingCallback = callback;//賦值callback
channel.port1.postMessage('');
// startTime = Date.now();
// setTimeout(() => {
// callback({ didTimeout: Date.now() > timeoutTime, timeRemaining });
// });
}
window.myIdle(callback, { timeout: 1000 })
- 還是剛纔那些代碼,只是寫了個myidle代替requestIdleCallback。
- 這裏不用channel用setTimeout一樣效果。
- 這裏速度有點快,可以發現數字會有個閃動過程,這就代表沒有阻塞渲染。
- 而阻塞渲染會怎樣呢?將while循環內改爲true,則不會進行遞歸調用,一直在主棧中進行調用,瀏覽器中看到的數字不會閃動,而是一出來已經確定的數字。