vue diff算法全解析
前言
我们知道 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 算法是将同层的节点进行比较,所以它的时间复杂度只有 o(n),它的算法非常的高效。 从代码中我们也能看出,patch 中会用 samevnode 判断老节点和新节点是否是同一个节点,如果是的话才会进行进一步的 patchvnode,否则就会创建新的 dom,移除旧的 dom。
samevnode
下面我们再来看看 samevnode 中是如何来判定两个节点是同一个节点的。
samevnode 通过比较两个节点的 key、tag、注释节点、数据信息是否相等来判断两个 node 节点是否是相同节点,对 input 标签做了一个单独的判断,为了兼容不同浏览器。
patchvnode
patchvnode 的过程是这样的:
- 如果 oldvnode 和 vnode 是同一个对象,那么久直接返回,不需要再更新
- 如果新旧vnode都是静态的,同时它们的key相同(代表同一节点),并且新的vnode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentinstance即可。
- 如果vnode.text不是文本节点,新老节点均有children子节点,且新老节点的子节点不相同的时候,则对子节点进行diff操作,调用updatechildren,这个updatechildren也是diff的核心。
- 如果老节点没有子节点而新节点存在子节点,先清空老节点dom的文本内容,然后为当前dom节点加入子节点
- 当新节点没有子节点而老节点有子节点的时候,则移除该dom节点的所有子节点。
- 当新老节点都无子节点的时候,只是文本的替换。
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算法的资料请关注其它相关文章!