发生的时机
在异步更新时,通过渲染函数(在另一篇文章中)的执行,生成虚拟vnode(createElement 返回的是虚拟vnode),之后,需要进行新旧节点比较。
为什么用key
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key, Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。---如果存在孙子组件的情况下,会出bug 而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。 #双端比较,也叫两头比较,顾名思义就是首尾各自比较。
- 采取这种比较算法的原因是在日常业务开发中,发现是某个节点为位置从某个位置换到了后面某个位置上,这种算法是一种效率和实际业务的平衡。
- 核心思路是新旧节点的虚拟dom两头分别设置索引,加上dom原生方法
insertBefore
移动节点,达到尽量复用的效果。 - vnode 进行算法比较,但是不改变vnode的顺序,操作的是旧节点上保留的指向的原生dom.
- 举例
a b c d //旧的虚拟dom ,4个li节点元素,也代表了旧dom的顺序,索引值初始化: oldStart = 0; oldEnd = 3
d a c b // 新的 newStart = 0; newEnd = 3
步骤如下
第一次比较:
1、 第一行的第一个元素a 和第二行的第一个元素d 比较,不相同,不处理。 2、 第一行的最后一个元素d和第二行的最后一个元素b比较,不相同,不处理。 3、 第一行的第一个元素a和第二行的最后一个元素b比较,不相同,不处理。 4、 第一行的最后一个元素d和第二行的第一个元素d比较,相同,说明旧dom的最后一个元素到了第一个了,需要把最后元素移动到首位。
// 获取到父元素的dom, 获取到最后一个虚拟节点里面保留的真实dom。通过oldEndVnode.el
parentNode.insertBefore(oldEndVnode.el, oldStartVnode.el)
5、然后第一行 结束索引减1 ,第二行开始索引加1
a b c //oldStart = 0; oldEnd = 2
a c b //newStart = 1; newEnd = 3
第二次比较:
1、第一行的第一个元素a 和第二行的第一个元素a 比较,相同,说明位置不变,不移动节点,但是开始索引都得加1
b c //oldStart = 1; oldEnd = 2
c b //newStart = 2; newEnd = 3
第三次比较:
1、 第一行的第一个元素b 和第二行的第一个元素c 比较,不相同,不处理。 2、 第一行的最后一个元素c和第二行的最后一个元素b比较,不相同,不处理。 3、 第一行的第一个元素b和第二行的最后一个元素b比较,相同,说明b所在的节点已经从开始移到当前(正在比较的新旧vnode)的dom末尾了
// 获取到父元素的dom, 获取到最后一个虚拟节点里面保留的真实dom。
parentNode.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling() || null) // 这里移动到末尾,可能会遇到null, 表示我要移动到最后一个节点后面节点也就是空节点前面。
c //oldStart = 2; oldEnd = 2
c//newStart = 2; newEnd = 2
第四次比较:
1、 第一行的第一个元素c 和第二行的第一个元素c 比较,相同,说明位置不变,不移动节点,但是开始索引都得加1
c //oldStart = 3; oldEnd = 2
c//newStart = 3; newEnd = 2
- 当开始索引都大于结束节点时说明已经比较完了(新旧节点个数不一样的情况,下次再补充)。
- 至此,一个双端比较的思路模型出炉。