vue3 源碼解讀之 time slicing

hello,你們好,我是 132,很久不賤……vue

今天給你們帶來一篇源碼解析的文章,emm 是關於 vue3 的,vue3 源碼放出後,已經有不少文章來分析它的源碼,我以爲很快又要爛大街了,哈哈react

不過今天我要解析的部分是已經被廢除的 time slicing 部分,這部分源碼曾經出如今 vue conf 2018 的視頻中,可是源碼已經被移除掉了,以後可能也不會有人關注,因此應該不會爛大街git

打包

閱讀源碼以前,須要先進行打包,打包出一份乾淨可調試的文件很重要github

vue3 使用的 rollup 進行打包,咱們須要先對它進行改造面試

import cleanup from 'rollup-plugin-cleanup'
plugins: [
    cleanup() //增長了一個 cleanup 插件
      
    tsPlugin,
    aliasPlugin,
    createReplacePlugin(isProductionBuild, isBunlderESMBuild, isCompat),
    ...plugins
],
複製代碼

增長 cleanup 插件主要目的是打包出無註釋的文件數組

以上,是我我的閱讀源碼的習慣,我以爲註釋和類型的做用就是礙眼的,因此先去掉再說app

用例

咱們在讀源碼以前,須要先實現一個正確用例,可是我讀的這個版本的源碼,仍是 class 的,怎麼辦?dom

這個時候咱們能夠根據測試用例來猜想並給出代碼異步

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)
    ]
  }
}
複製代碼

很好,如今咱們有了一個爭取,簡單的用例了,接下來就是一股腦調試函數

調試

因爲我在 fre 中也實現了時間切片,因此我對它很是瞭解,我知道它的做用原理,因此咱們直接搜索宏任務,哈,果真有

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, `*`);
}
複製代碼

這段代碼很是容易理解,就是在宏任務隊列裏執行了 flush 函數,繼續

而後關鍵就來了

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; // 此處爲關鍵,意思是超過16ms,或者任務過時,跳出循環
            }
        }
    }
    ... 如下代碼省略...
複製代碼

上面的循環很關鍵,它作的事情很簡單的,從 stageQueue 裏出棧一個任務,而後執行 stateJob

stateJob 作的事情很簡單,就是往 commitQueue 裏 push 這個任務

function stageJob(job) {
    if (job.ops.length === 0) {
        currentJob = job;
        job.cleanup = job();
        currentJob = null;
        commitQueue.push(job); //重點在這裏
        job.status = 2;
    }
}
複製代碼

到目前爲止,咱們源碼讀了一丟丟,可是已經幾乎讀完了能夠說

它的本質就是,在宏任務中,stageQueue 做爲低優先級任務隊列,不斷的出棧,而後分批次(16ms 的閾值)入棧到 commitQueue 裏

呼,其實若是不是寫文章,就能夠到此爲止了,可是寫文章爲了湊字數嘛,咱們繼續

上面咱們已經知道了兩個隊列,stageQueue 和 commitQueue,可是並不知道他們裏面都是什麼東西

是什麼東西被調度的呢?打印一下,你就知道:

console.log(stageQueue,commitQueue)
複製代碼

得出的結果是

function mountComponentInstance(){...}
複製代碼

看名字就知道是組件掛載函數,固然組件更新和卸載的函數也是同理

到如今,咱們也知道了參與調度的是組件掛載更新的函數,因此本質上,vue 的時間切片的基本單位是組件,也就是說,若是你的組件掛載須要一個小時,那你仍然要卡一小時

湊字數

剩下的內容純屬湊字數,就是除了核心調度以外的東西

好比 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(){}]
複製代碼

湊合湊合,是個數組,包含了 dom 操做的方法和被操做的元素

而後這個過程是同步完成的,也就是所謂的高優先級任務,必須等到完全收集完畢,才能夠循環執行它

作完這個,postEffectQueue 主要是一些額外的反作用和清理工做,我實在湊字數無能,就不打印了

總結

最後咱們用最直白的話,總結一下:

在宏任務隊列中,不斷的從 stageQueue 分批次(16ms)將組件的函數轉移到 commitQueue 裏,轉移完了,同步操做 dom

原理其實仍是利用了宏任務隊列,其實如今 vue 的作法和 fre 也有一點點相似,fre 是在宏任務中,儘量更多的去訪問 reconcile 大循環

關於廢除

如開頭提到的,time slicing 這部份內容已經在 master 分支被移除了,關於爲何廢除,我特意發了 issue,能夠戳這裏:(天啊,我和尤終於能夠和平地進行交談了)

github.com/vuejs/rfcs/…

簡單說,就是 time slicing 的收益不大,除了 issue 中提到的,它自己的場景就少的可憐

也由於 vue 如今的實現,因爲調度的基本單位是組件,因此它仍然會由於組件內部的邏輯而被阻斷

好比我把用例中用於阻斷的 block 函數改成 1s,就已經完全卡死了

思考

從 issue 和源碼自己,咱們能夠思考一些問題,同時用來湊字數

時間切片是否必須?

答案是否認的,尤的回覆已經足夠充分了:github.com/vuejs/rfcs/…

大體有兩點:

  1. 除了高幀率動畫,其餘的場景幾乎均可以使用防抖和節流去提升響應性能
  2. vue 如今的實現,粒度太大,最終的效果十分有限,不值得

那,fre 呢?

fre 的異步渲染,是否也存在這個問題,不得不認可,fre 雖然粒度很小,對於組件內部的阻斷能夠搞定,可是元素自己也能夠被阻斷

並且第一個問題也是存在的,就是沒有太多適用場景

可是 fre 源碼層面仍是意義重大的,即使這玩意搞出來,發現它做用不大,反作用不小,但 fre 做爲我我的的學習和研究的項目,它的價值歷來就不是業務層面的

只是我應該停下來,異步渲染搞定了,只是向你們展現它的源碼實現,將來不該該跟隨 react 去搞一堆業務 API,如 useTransition 等等

關於源碼?

vue3 發版當天,源碼解讀就放出了,可是到目前爲止,全部的源碼解讀通通都是蹭熱度的

不久的未來,vue 的源碼又要爛大街了……

這種現象引發檢討,咱們讀源碼究竟是爲了什麼?爲了面試嗎?爲了更好的寫業務?

對我而言,僅僅只是感興趣,我對這部分源碼感興趣,我就去讀,而且只讀感興趣的部分

其實你們也看到了,我不多寫源碼解讀的文章,由於我一直反對所謂的【通讀源碼】

將閱讀源碼做爲一項工做,一樣的小函數,讀了一遍又一遍,重複勞動

這和糊 shi 有什麼區別呢?

仁者見仁,我溜啦!

相關文章
相關標籤/搜索