阅读 1168

Vue更新流程nextTick详解

Vue更新流程nextTick详解 接上一篇初始化流程

前言

这一篇主要会讲解vue的异步更新策略,包括虚拟dom和diff详细过程(还是最好对着源码流程看)源码下载以及配置调试工具的过程在上一篇都有讲解这篇文章不在赘述

上一篇链接:juejin.cn/post/692310…

建议

本篇幅可能需要阅读半个小时左右,可以泡上一杯咖啡和花茶慢慢看,但是我觉得花上一些时间弄懂这些还是值得的

开始我们的操作

1

我们今天从core/observe/index.js 开始看

2
  • 这个方法将数据变成响应式之后

  • 当数据发生变化就会触发dep.notify进入更新流程

接下来看一下dep.notify方法内部执行的操作

  • 复制了subs副本(这个subs数组里面的参数是在执行defineReactive get的时候将watcher放进去的)
  • 执行update开始更新watcher队列

接下来我们看update内部执行过程 update方法位置在 core/observer/watcher.js

  • this.lazy当计算属性computed生成的watcher就会执行这个
  • this.sync是当用户主动设置同步方式执行才会执行的操作一般不会这样做
  • 我们这次主要看执行queueWatcher异步更新的过程

我们继续看queueWatcher内部流程queueWatcher方法位于core/observe/schedular

  • 首先可以看到has会根据watcher里面的id进行去重判断(has就是一个对象)

  • watcher放到queue中等待将来一起更新

  • 在往下继续走我们看到了nextTick 注意:这里的nextTick就是我们在页面中用的this.$nextTIck和vue.nextTick

  • 我们可以先看一下当前在异步流程中nexTIck传入的参数flushSchedulerQueue,这对我们后面的理解比较重要

flushSchedulerQueue内部

  • 之后我们会主要看这个run方法,目前先看到这里 重点是要分清nextTick接收的是组件中传来的方法还是异步更新过程中传入的方法这个很重要

接下来我们看nextTick流程

  • 我们可以看到这里将传进来的cb方法包装到一个方法中push到了callbacks内部
  • 当前方法内部做了错误兼容,并且未来会执行cb方法
  • 接下来会判断pending如果不是处于挂起的状态我们就可以开始执行了timerFunc
  • timerFuncs是全局的变量

接下来我们看一下timerFuncs是怎么执行的

  • 内部首选的是使用promise.then以微任务的方式去执行
  • 如果当前环境不支持promise的情况会继续向下走使用MutationObserver的方式去执行
  • 如果以上的方式都不支持就会使用setTimeout的方式

之后我们看timerFunc 执行的flushCallbacks方法

  • 复制了callbacks副本
  • 当前执行的方法 如果是异步更新流程情况下那么执行的就是我们上面所说的flushSchedulerQueue
  • 我们继续看flushSchedulerQueue内部的run方法,到这里就和上面说的串起来了

我们看flushSchedulerQueue 内部的run方法

  • 方法内部主要执行的是get方法

继续看get方法内部

  • 方法内部执行getter方法
  • getter方法就是watcher实例化的时候传进来的第二个方法,也就是当前组件的更新方法
  • 总结来说flushSchedulerQueue 内部的run方法实际调用的就是当前组件的更新函数
  • 如果你看过上一篇文章应该能知道这个更新函数是在哪里实例化传进来的,没看过也没关系我们现在重新去看一次

其实就是在挂载的过程中执行的实例化,挂载过程执行的函数$mount -> mountComponent 位置在core/instance/lifecycle.js

image-20210209121416482
  • 将更新方法赋值给updateComponent
  • 更新方法内部执行的是_update方法这里接收的第一个参数是组件的渲染函数_render也就是说将来_update方法内部接收的是_render返回的值也就是转化完的vdom
  • new Watcher的时候将updateComponent传进去,这里的updateComponent对应的就是上面说的getter

我们来看_render方法的内部,位置在core/instance/render.js里面

  • 我们可以直接看到_render返回值就是一个vnode
  • 82行就是转化vnode的过程
  • render.call接收了两个参数,最后的vm.$createElement就是我们render方法中的那个h函数(下一篇文章中讲怎么将模板语法树转换成渲染函数的时候,会讲怎么转换vnode的过程)

接下来我们继续看_update方法内部做了啥,位置在core/instance/lifecycle.js

  • 这里会判断prevVnode如果存在证明是更新过程否则就是初始话流程
  • 我们可以看到都会调用__patch__方法不同的是初始化流程传的第一个参数是宿主元素,更新过程传的是上一个的vnode
  • 这里走完之后页面也就更新完成了

之后我们继续看__patch__方法内部是怎么比较更新的,位置在core/vdom/patch.js里面大概在700行左右函数有点长会截成两个图重点看里面的注释

我们本次主要看更新的时候diff的过程重点关注patchVnode方法,我们需要知道几个vue的diff更新策略这对我们理解diff过程比较重要

  • 比较两个VNode,只有三种类型操作:属性更新、文本更新、子节点更新

两组节点更新的条件

  • 比较的过程主要是通过双指针的方式向中间靠拢做比较

  • 首部和首部进行比较、尾部和尾部进行比较、老节点的首部和新节点的尾部进行比较、老节点的尾部和新节点的首部进行比较、主要以老节点列表为准去替换老节点的位置

  • 新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren

  • 如果新节点有子节点而老节点没有子节点,先清空老节点的文本内容,然后为其新增子节点

  • 当新节点没有子节点而老节点有子节点的时候,则移除该节点的所有子节点

  • 当新老节点都无子节点的时候,只做文本的替换

之后我们继续看patchVnode方法内部,这里会把更新过程的代码贴出来并且会有注释标注,代码有点长分两个截图

接下来我们看updateChildren这里是重点也是两组节点diff的过程,也会把注释都贴在里面标注出来代码有点长分两张截图

最后我们看一下替换过程的详细操作,是根据什么来决定当前的节点是否可以复用,这里也设计到一个经典的面试题 key到底干嘛的,代码里主要就是sameVnode方法我们看一下这个方法内部怎么判断的

  • 这里我们可以看到使用key来判断是否往下继续比较
  • 当你在页面中绑定了key是下标的话只能是在页面中看着不报错实际和不绑定key操作是一样的
  • 不绑定默认就是undefined还会继续往后比较,如果绑定的是下标的情况那么默认两组下标都是一样的也会往后走这样key就失去作用了所以大家尽量在开发的时候还是用唯一的 uuid如果能确定两组节点不会进行更新也可以绑定下标为了页面不提示error
  • 后面还有tag等属性会继续判断

结语

创作不易如果大家觉得有帮助不妨点个赞😁

新年了 最后给大家拜个年 新年快乐、万事吉祥

3
文章分类
前端
文章标签