vue3 源碼解讀之 time slicing的使用方法
今天給大家?guī)硪黄创a解析的文章,emm 是關(guān)于 vue3 的,vue3 源碼放出后,已經(jīng)有很多文章來分析它的源碼,我覺得很快又要爛大街了,哈哈
不過今天我要解析的部分是已經(jīng)被廢除的 time slicing 部分,這部分源碼曾經(jīng)出現(xiàn)在 vue conf 2018 的視頻中,但是源碼已經(jīng)被移除掉了,之后可能也不會有人關(guān)注,所以應(yīng)該不會爛大街
打包
閱讀源碼之前,需要先進行打包,打包出一份干凈可調(diào)試的文件很重要
vue3 使用的 rollup 進行打包,我們需要先對它進行改造
import cleanup from 'rollup-plugin-cleanup' plugins: [ cleanup() //增加了一個 cleanup 插件 tsPlugin, aliasPlugin, createReplacePlugin(isProductionBuild, isBunlderESMBuild, isCompat), ...plugins ],
增加 cleanup 插件主要目的是打包出無注釋的文件
以上,是我個人閱讀源碼的習(xí)慣,我覺得注釋和類型的作用就是礙眼的,所以先去掉再說
用例
我們在讀源碼之前,需要先實現(xiàn)一個正確用例,但是我讀的這個版本的源碼,還是 class 的,怎么辦?
這個時候我們可以根據(jù)測試用例來猜測并給出代碼
function block () {
const start = performance.now()
while (performance.now() - start < 2) {
}
}
class Test extend Component {
render (props) {
block()
return h('li', props.msg)
}
}
class App extend Component {
msg = ''
render () {
const list = []
for (let i = 0; i < 200; i++) {
list.push(h(Test, { key: i, msg: this.msg }))
}
return [
h('input', {
onInput: e => {
this.msg = e.target.value
}
}),
h('div',list)
]
}
}
很好,現(xiàn)在我們有了一個爭取,簡單的用例了,接下來就是一股腦調(diào)試
調(diào)試
由于我在 fre 中也實現(xiàn)了時間切片,所以我對它非常了解,我知道它的作用原理,所以我們直接搜索宏任務(wù),哈,果然有
window.addEventListener('message', event => {
if (event.source !== window || event.data !== key) {
return;
}
flushStartTimestamp = getNow();
try {
flush();
}
catch (e) {
handleError(e);
}
}, false);
function flushAfterMacroTask() {
window.postMessage(key, `*`);
}
這段代碼非常容易理解,就是在宏任務(wù)隊列里執(zhí)行了 flush 函數(shù),繼續(xù)
然后關(guān)鍵就來了
function flush() {
let job;
while (true) {
job = stageQueue.shift();
if (job) {
stageJob(job);
}
else {
break;
}
{
const now = getNow();
if (now - flushStartTimestamp > frameBudget && job.expiration > now) {
break; // 此處為關(guān)鍵,意思是超過16ms,或者任務(wù)過期,跳出循環(huán)
}
}
}
... 以下代碼省略...
上面的循環(huán)很關(guān)鍵,它做的事情很簡單的,從 stageQueue 里出棧一個任務(wù),然后執(zhí)行 stateJob
stateJob 做的事情很簡單,就是往 commitQueue 里 push 這個任務(wù)
function stageJob(job) {
if (job.ops.length === 0) {
currentJob = job;
job.cleanup = job();
currentJob = null;
commitQueue.push(job); //重點在這里
job.status = 2;
}
}
到目前為止,我們源碼讀了一丟丟,但是已經(jīng)幾乎讀完了可以說
它的本質(zhì)就是,在宏任務(wù)中,stageQueue 作為低優(yōu)先級任務(wù)隊列,不斷的出棧,然后分批次(16ms 的閾值)入棧到 commitQueue 里
呼,其實如果不是寫文章,就可以到此為止了,但是寫文章為了湊字數(shù)嘛,我們繼續(xù)
上面我們已經(jīng)知道了兩個隊列,stageQueue 和 commitQueue,但是并不知道他們里面都是什么東西
是什么東西被調(diào)度的呢?打印一下,你就知道:
console.log(stageQueue,commitQueue)
得出的結(jié)果是
function mountComponentInstance(){...}
看名字就知道是組件掛載函數(shù),當(dāng)然組件更新和卸載的函數(shù)也是同理
到現(xiàn)在,我們也知道了參與調(diào)度的是組件掛載更新的函數(shù),所以本質(zhì)上,vue 的時間切片的基本單位是組件,也就是說,如果你的組件掛載需要一個小時,那你仍然要卡一小時
湊字數(shù)
剩下的內(nèi)容純屬湊字數(shù),就是除了核心調(diào)度之外的東西
比如 commitQueue 是操作 dom 的,那它咋個操作
function commitJob(job) {
const { ops, postEffects } = job;
for (let i = 0; i < ops.length; i++) {
applyOp(ops[i]); // 重點在這里
}
if (postEffects) {
postEffectsQueue.push(...postEffects);
}
resetJob(job);
job.status = 0;
}
如上,拿到 ops,然后進行操作,我們看一下 ops 是啥就行了
[<div></div>, <li></li>, function CreactElement(){}]
湊合湊合,是個數(shù)組,包含了 dom 操作的方法和被操作的元素
然后這個過程是同步完成的,也就是所謂的高優(yōu)先級任務(wù),必須等到徹底收集完畢,才可以循環(huán)執(zhí)行它
做完這個,postEffectQueue 主要是一些額外的副作用和清理工作,我實在湊字數(shù)無能,就不打印了
總結(jié)
最后我們用最直白的話,總結(jié)一下:
在宏任務(wù)隊列中,不斷的從 stageQueue 分批次(16ms)將組件的函數(shù)轉(zhuǎn)移到 commitQueue 里,轉(zhuǎn)移完了,同步操作 dom
原理其實還是利用了宏任務(wù)隊列,其實現(xiàn)在 vue 的做法和 fre 也有一點點類似,fre 是在宏任務(wù)中,盡可能更多的去訪問 reconcile 大循環(huán)
關(guān)于廢除
如開頭提到的,time slicing 這部分內(nèi)容已經(jīng)在 master 分支被移除了,關(guān)于為什么廢除,我特地發(fā)了 issue,可以戳這里:(天啊,我和尤終于可以和平地進行交談了)
https://github.com/vuejs/rfcs/issues/89
簡單說,就是 time slicing 的收益不大,除了 issue 中提到的,它本身的場景就少的可憐
也因為 vue 現(xiàn)在的實現(xiàn),由于調(diào)度的基本單位是組件,所以它仍然會因為組件內(nèi)部的邏輯而被阻斷
比如我把用例中用于阻斷的 block 函數(shù)改為 1s,就已經(jīng)徹底卡死了
思考
從 issue 和源碼本身,我們可以思考一些問題,同時用來湊字數(shù)
時間切片是否必須?
答案是否定的,尤的回復(fù)已經(jīng)足夠充分了:https://github.com/vuejs/rfcs/issues/89#issuecomment-546988615
大致有兩點:
- 除了高幀率動畫,其他的場景幾乎都可以使用防抖和節(jié)流去提高響應(yīng)性能
- vue 現(xiàn)在的實現(xiàn),粒度太大,最終的效果十分有限,不值得
那,fre 呢?
fre 的異步渲染,是否也存在這個問題,不得不承認,fre 雖然粒度很小,對于組件內(nèi)部的阻斷可以搞定,但是元素本身也可以被阻斷
而且第一個問題也是存在的,就是沒有太多適用場景
但是 fre 源碼層面還是意義重大的,即便這玩意搞出來,發(fā)現(xiàn)它作用不大,副作用不小,但 fre 作為我個人的學(xué)習(xí)和研究的項目,它的價值從來就不是業(yè)務(wù)層面的
只是我應(yīng)該停下來,異步渲染搞定了,只是向大家展示它的源碼實現(xiàn),未來不應(yīng)該跟隨 react 去搞一堆業(yè)務(wù) API,如 useTransition 等等
關(guān)于源碼?
vue3 發(fā)版當(dāng)天,源碼解讀就放出了,但是到目前為止,所有的源碼解讀統(tǒng)統(tǒng)都是蹭熱度的
不久的將來,vue 的源碼又要爛大街了……
這種現(xiàn)象引起反省,我們讀源碼到底是為了什么?為了面試嗎?為了更好的寫業(yè)務(wù)?
對我而言,僅僅只是感興趣,我對這部分源碼感興趣,我就去讀,并且只讀感興趣的部分
其實大家也看到了,我很少寫源碼解讀的文章,因為我一直反對所謂的【通讀源碼】
將閱讀源碼作為一項工作,同樣的小函數(shù),讀了一遍又一遍,重復(fù)勞動
這和糊 shi 有什么區(qū)別呢?
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Object.assign觸發(fā)watch原理示例解析
這篇文章主要為大家介紹了Object.assign觸發(fā)watch原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
vue3+vite 動態(tài)引用靜態(tài)資源及動態(tài)引入assets文件夾圖片的多種方式
通過require動態(tài)引入, 發(fā)現(xiàn)報錯:require is not defind,這是因為 require 是屬于 Webpack 的方法,本文給大家介紹vue3+vite 動態(tài)引用靜態(tài)資源及動態(tài)引入assets文件夾圖片的多種方式,感興趣的朋友一起看看吧2023-10-10
Vue動態(tài)權(quán)限登錄實現(xiàn)(基于路由與角色)
很多應(yīng)用都會需要對不同的用戶進行權(quán)限控制,本文主要介紹了Vue動態(tài)權(quán)限登錄實現(xiàn)(基于路由與角色),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
vue在使用element組件出現(xiàn)<el-input>標簽無法輸入的問題
這篇文章主要介紹了vue在使用element組件出現(xiàn)<el-input>標簽無法輸入的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
vuejs使用axios異步訪問時用get和post的實例講解
今天小編就為大家分享一篇vuejs使用axios異步訪問時用get和post的實例講解,具有很好的參考價值。希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08

