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,能够戳这里:(天啊,我和尤终于能够和平地进行交谈了)
简单说,就是 time slicing 的收益不大,除了 issue 中提到的,它自己的场景就少的可怜
也由于 vue 如今的实现,因为调度的基本单位是组件,因此它仍然会由于组件内部的逻辑而被阻断
好比我把用例中用于阻断的 block 函数改成 1s,就已经完全卡死了
从 issue 和源码自己,咱们能够思考一些问题,同时用来凑字数
答案是否认的,尤的回复已经足够充分了:github.com/vuejs/rfcs/…
大体有两点:
那,fre 呢?
fre 的异步渲染,是否也存在这个问题,不得不认可,fre 虽然粒度很小,对于组件内部的阻断能够搞定,可是元素自己也能够被阻断
并且第一个问题也是存在的,就是没有太多适用场景
可是 fre 源码层面仍是意义重大的,即使这玩意搞出来,发现它做用不大,反作用不小,但 fre 做为我我的的学习和研究的项目,它的价值历来就不是业务层面的
只是我应该停下来,异步渲染搞定了,只是向你们展现它的源码实现,将来不该该跟随 react 去搞一堆业务 API,如 useTransition 等等
vue3 发版当天,源码解读就放出了,可是到目前为止,全部的源码解读通通都是蹭热度的
不久的未来,vue 的源码又要烂大街了……
这种现象引发检讨,咱们读源码究竟是为了什么?为了面试吗?为了更好的写业务?
对我而言,仅仅只是感兴趣,我对这部分源码感兴趣,我就去读,而且只读感兴趣的部分
其实你们也看到了,我不多写源码解读的文章,由于我一直反对所谓的【通读源码】
将阅读源码做为一项工做,一样的小函数,读了一遍又一遍,重复劳动
这和糊 shi 有什么区别呢?