欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vue diff算法全解析

程序员文章站 2022-04-26 09:13:54
前言我们知道 vue 使用的是虚拟 dom 去减少对真实 dom 的操作次数,来提升页面运行的效率。今天我们来看看当页面的数据改变的时候,vue 是如何来更新 dom 的。vue和react在更新do...

前言

我们知道 vue 使用的是虚拟 dom 去减少对真实 dom 的操作次数,来提升页面运行的效率。今天我们来看看当页面的数据改变的时候,vue 是如何来更新 dom 的。vue和react在更新dom时,使用的算法基本相同,都是基于 。 当页面上的数据发生变化时,vue 不会立即渲染。而是经过 diff 算法,判断出哪些是不需要变化的,哪些是需要变化更新的,只需要更新那些需要更新的 dom 就可以了,这样就减少了很多不必要的 dom 操作,大大提升了性能。 vue就使用了这样的抽象节点vnode,它是对真实dom的一层抽象,而不依赖某个平台,它可以是浏览器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象dom树进行创建删除修改等操作,这也为前后端同构提供了可能。

vue 更新视图

我们知道在 vue 1.x 中,每一个数据都对应一个 watcher;而在 vue 2.x 中,一个组件对应一个 watcher,这样当我们的数据改变的时候,在 set 函数中会触发 dep 的 notify 函数去通知 watcher 去执行 vm._update(vm._render(), hydrating) 方法去更新视图,下面我们来看看 _update 方法

很明显,我们能看到 _update 方法会将传入的 vnode 将老的 vnode 进行 patch 操作。 下面我们再来看看在 patch 函数中都发生了什么。

patch

patch 函数将新老两个节点进行比较,然后判断出哪些是需要修改的节点,只需要修改这些节点即可,这样可以比较高效地更新 dom,我们先来看一下代码

vue diff算法全解析

vue 的 diff 算法是将同层的节点进行比较,所以它的时间复杂度只有 o(n),它的算法非常的高效。 从代码中我们也能看出,patch 中会用 samevnode 判断老节点和新节点是否是同一个节点,如果是的话才会进行进一步的 patchvnode,否则就会创建新的 dom,移除旧的 dom。

samevnode

下面我们再来看看 samevnode 中是如何来判定两个节点是同一个节点的。

samevnode 通过比较两个节点的 key、tag、注释节点、数据信息是否相等来判断两个 node 节点是否是相同节点,对 input 标签做了一个单独的判断,为了兼容不同浏览器。

patchvnode

patchvnode 的过程是这样的:

  1. 如果 oldvnode 和 vnode 是同一个对象,那么久直接返回,不需要再更新
  2. 如果新旧vnode都是静态的,同时它们的key相同(代表同一节点),并且新的vnode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentinstance即可。
  3. 如果vnode.text不是文本节点,新老节点均有children子节点,且新老节点的子节点不相同的时候,则对子节点进行diff操作,调用updatechildren,这个updatechildren也是diff的核心。
  4. 如果老节点没有子节点而新节点存在子节点,先清空老节点dom的文本内容,然后为当前dom节点加入子节点
  5. 当新节点没有子节点而老节点有子节点的时候,则移除该dom节点的所有子节点。
  6. 当新老节点都无子节点的时候,只是文本的替换。

updatechildren

我们页面的dom是一个树状结构,上面所讲的patchvnode方法,是复用同一个dom元素,而如果新旧两个vnnode对象都有子元素,我们应该怎么去比较复用元素呢?这就是我们updatechildren方法所要做的事儿

下面是复制其他博主的文章,觉得写的挺好的 原文地址 :github.com/liutao/vue2…


乍一看这一块代码,可能有点儿懵。具体内容其实不复杂,我们先大体看一下整个判断流程,之后通过几个例子来详细过一下。

oldstartidx、newstartidx、oldendidx、newendidx都是指针,具体每一个指什么,相信大家都很明了,我们整个比较的过程,会不断的移动指针。

oldstartvnode、newstartvnode、oldendvnode、newendvnode与上面的指针一一对应,是它们所指向的vnode结点。

while循环在oldch或newch遍历结束后停止,否则会不断的执行循环流程。整个流程分为以下几种情况:

1、 如果oldstartvnode未定义,则oldch数组遍历的起始指针后移一位。

注:见第七种情况,key值相同可能会置为undefined

2、 如果oldendvnode未定义,则oldch数组遍历的起始指针前移一位。

注:见第七种情况,key值相同可能会置为undefined

3、samevnode(oldstartvnode, newstartvnode),这里判断两个数组起始指针所指向的对象是否可以复用。如果返回真,则先调用patchvnode方法复用dom元素并递归比较子元素,然后oldch和newch的起始指针分别后移一位。

4、samevnode(oldendvnode, newendvnode),这里判断两个数组结束指针所指向的对象是否可以复用。如果返回真,则先调用patchvnode方法复用dom元素并递归比较子元素,然后oldch和newch的结束指针分别前移一位。

5、samevnode(oldstartvnode, newendvnode),这里判断oldch起始指针指向的对象和newch结束指针所指向的对象是否可以复用。如果返回真,则先调用patchvnode方法复用dom元素并递归比较子元素,因为复用的元素在newch中是结束指针所指的元素,所以把它插入到oldendvnode.elm的前面。最后oldch的起始指针后移一位,newch的起始指针分别前移一位。

6、samevnode(oldendvnode, newstartvnode),这里判断oldch结束指针指向的对象和newch起始指针所指向的对象是否可以复用。如果返回真,则先调用patchvnode方法复用dom元素并递归比较子元素,因为复用的元素在newch中是起始指针所指的元素,所以把它插入到oldstartvnode.elm的前面。最后oldch的结束指针前移一位,newch的起始指针分别后移一位。

7、如果上述六种情况都不满足,则走到这里。前面的比较都是头尾组合的比较,这里的情况,稍微更加复杂一些,其实主要就是根据key值来复用元素。

① 遍历oldch数组,找出其中有key的对象,并以key为键,索引值为value,生成新的对象oldkeytoidx。

② 查询newstartvnode是否有key值,并查找oldkeytoidx是否有相同的key。

③ 如果newstartvnode没有key或oldkeytoidx没有相同的key,则调用createelm方法创建新元素,newch的起始索引后移一位。

④ elmtomove保存的是要移动的元素,如果samevnode(elmtomove, newstartvnode)返回真,说明可以复用,这时先调用patchvnode方法复用dom元素并递归比较子元素,重置oldch中相对于的元素为undefined,然后把当前元素插入到oldstartvnode.elm前面,newch的起始索引后移一位。如果samevnode(elmtomove, newstartvnode)返回假,例如tag名不同,则调用createelm方法创建新元素,newch的起始索引后移一位。

以上就是vue diff算法的使用的详细内容,更多关于vue diff算法的资料请关注其它相关文章!

相关标签: vue 算法 diff