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 有什么区别呢?

仁者见仁,我溜啦!

相关文章
相关标签/搜索