vue3 源码解读之 time slicing的使用方法


Posted in Javascript onOctober 31, 2019

今天给大家带来一篇源码解析的文章,emm 是关于 vue3 的,vue3 源码放出后,已经有很多文章来分析它的源码,我觉得很快又要烂大街了,哈哈

不过今天我要解析的部分是已经被废除的 time slicing 部分,这部分源码曾经出现在 vue conf 2018 的视频中,但是源码已经被移除掉了,之后可能也不会有人关注,所以应该不会烂大街

打包

阅读源码之前,需要先进行打包,打包出一份干净可调试的文件很重要

vue3 使用的 rollup 进行打包,我们需要先对它进行改造

import cleanup from 'rollup-plugin-cleanup'
plugins: [
  cleanup() //增加了一个 cleanup 插件
   
  tsPlugin,
  aliasPlugin,
  createReplacePlugin(isProductionBuild, isBunlderESMBuild, isCompat),
  ...plugins
],

增加 cleanup 插件主要目的是打包出无注释的文件

以上,是我个人阅读源码的习惯,我觉得注释和类型的作用就是碍眼的,所以先去掉再说

用例

我们在读源码之前,需要先实现一个正确用例,但是我读的这个版本的源码,还是 class 的,怎么办?

这个时候我们可以根据测试用例来猜测并给出代码

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,可以戳这里:(天啊,我和尤终于可以和平地进行交谈了)

https://github.com/vuejs/rfcs/issues/89

简单说,就是 time slicing 的收益不大,除了 issue 中提到的,它本身的场景就少的可怜

也因为 vue 现在的实现,由于调度的基本单位是组件,所以它仍然会因为组件内部的逻辑而被阻断

比如我把用例中用于阻断的 block 函数改为 1s,就已经彻底卡死了

思考

从 issue 和源码本身,我们可以思考一些问题,同时用来凑字数

时间切片是否必须?

答案是否定的,尤的回复已经足够充分了:https://github.com/vuejs/rfcs/issues/89#issuecomment-546988615

大致有两点:

  • 除了高帧率动画,其他的场景几乎都可以使用防抖和节流去提高响应性能
  • vue 现在的实现,粒度太大,最终的效果十分有限,不值得

那,fre 呢?

fre 的异步渲染,是否也存在这个问题,不得不承认,fre 虽然粒度很小,对于组件内部的阻断可以搞定,但是元素本身也可以被阻断

而且第一个问题也是存在的,就是没有太多适用场景

但是 fre 源码层面还是意义重大的,即便这玩意搞出来,发现它作用不大,副作用不小,但 fre 作为我个人的学习和研究的项目,它的价值从来就不是业务层面的

只是我应该停下来,异步渲染搞定了,只是向大家展示它的源码实现,未来不应该跟随 react 去搞一堆业务 API,如 useTransition 等等

关于源码?

vue3 发版当天,源码解读就放出了,但是到目前为止,所有的源码解读统统都是蹭热度的
不久的将来,vue 的源码又要烂大街了……
这种现象引起反省,我们读源码到底是为了什么?为了面试吗?为了更好的写业务?
对我而言,仅仅只是感兴趣,我对这部分源码感兴趣,我就去读,并且只读感兴趣的部分
其实大家也看到了,我很少写源码解读的文章,因为我一直反对所谓的【通读源码】
将阅读源码作为一项工作,同样的小函数,读了一遍又一遍,重复劳动
这和糊 shi 有什么区别呢?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
理解Javascript_01_理解内存分配原理分析
Oct 11 Javascript
jquery 插件学习(五)
Aug 06 Javascript
JS获得QQ号码的昵称,头像,生日的简单实例
Dec 04 Javascript
angularjs创建弹出框实现拖动效果
Aug 25 Javascript
教你如何终止JQUERY的$.AJAX请求
Feb 23 Javascript
简单讲解jQuery中的子元素过滤选择器
Apr 18 Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
Oct 10 Javascript
详解Vue 2.0封装axios笔记
Jun 22 Javascript
JavaScript 用fetch 实现异步下载文件功能
Jul 21 Javascript
详解基于Vue-cli搭建的项目如何和后台交互
Jun 29 Javascript
vue打包使用Nginx代理解决跨域问题
Aug 27 Javascript
Vue axios获取token临时令牌封装案例
Sep 11 Javascript
vue data恢复初始化数据的实现方法
Oct 31 #Javascript
vue和iview实现Scroll 数据无限滚动功能
Oct 31 #Javascript
vue 使用鼠标滚动加载数据的例子
Oct 31 #Javascript
axios 实现post请求时把对象obj数据转为formdata
Oct 31 #Javascript
Javascript和jquery在selenium的使用过程
Oct 31 #jQuery
使用axios发送post请求,将JSON数据改为form类型的示例
Oct 31 #Javascript
vue封装可复用组件confirm,并绑定在vue原型上的示例
Oct 31 #Javascript
You might like
使用迭代器 遍历文件信息的详解
2013/06/08 PHP
PHP调用JAVA的WebService简单实例
2014/03/11 PHP
PHP判断是手机端还是PC端 PHP判断是否是微信浏览器
2017/03/15 PHP
Laravel源码解析之路由的使用和示例详解
2018/09/27 PHP
Window.Open如何在同一个标签页打开
2014/06/20 Javascript
js重写alert控件(适合学习js的新手朋友)
2014/08/24 Javascript
JavaScript中number转换成string介绍
2014/12/31 Javascript
MVVM模式中ViewModel和View、Model有什么区别?
2015/06/19 Javascript
BootStrap 动态添加验证项和取消验证项的实现方法
2016/09/28 Javascript
ES6概念 ymbol.for()方法
2016/12/25 Javascript
jquery精度计算代码 jquery指定精确小数位
2017/02/06 Javascript
vue一个页面实现音乐播放器的示例
2018/02/06 Javascript
vue中实现先请求数据再渲染dom分享
2018/03/17 Javascript
JavaScript中var、let、const区别浅析
2018/06/24 Javascript
jQuery实现侧边栏隐藏与显示的方法详解
2018/12/22 jQuery
vue+elementUi图片上传组件使用详解
2019/08/20 Javascript
vue路由传参页面刷新参数丢失问题解决方案
2019/10/08 Javascript
vue项目前端微信JSAPI与外部H5支付相关实现过程及常见问题
2020/04/14 Javascript
[04:19]DOTA2亚洲邀请赛 现场花絮
2015/03/11 DOTA
Python基础教程之内置函数locals()和globals()用法分析
2018/03/16 Python
Pandas_cum累积计算和rolling滚动计算的用法详解
2019/07/04 Python
python3.7 openpyxl 删除指定一列或者一行的代码
2019/10/08 Python
python使用pymongo与MongoDB基本交互操作示例
2020/04/09 Python
python3处理word文档实例分析
2020/12/01 Python
日本网路线上商品代购服务:转送JAPAN
2016/08/05 全球购物
Onzie官网:美国时尚瑜伽品牌
2019/08/21 全球购物
Seavenger官网:潜水服、浮潜、靴子和袜子
2020/03/05 全球购物
上海中网科技笔试题
2012/02/19 面试题
为什么需要版本控制
2016/10/28 面试题
大学生毕业自我评价范文分享
2013/11/11 职场文书
家庭教育先进个人事迹材料
2014/01/24 职场文书
领导班子四风问题个人对照检查材料
2014/10/04 职场文书
离婚律师函范本
2015/05/27 职场文书
Linux安装Nginx步骤详解
2021/03/31 Servers
Nginx代理Redis哨兵主从配置的实现
2022/07/15 Servers
SQL bool盲注和时间盲注详解
2022/07/23 SQL Server