function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
} while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) { // oldStartVnode没有,则oldStartIdx后移一位
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) { // oldEndVnode没有,则oldEndIdx前移一位
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) { // 处理 头部 的同类型节点,即oldStartVnode和newStartVnode指向同类节点的情况
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx] // 标记: oldStartIdx 和 newStartIdx 后移1位
} else if (sameVnode(oldEndVnode, newEndVnode)) { // 处理 尾部 的同类型节点,即oldEndVnode和newEndVnode指向同类节点的情况
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx] // 标记: oldEndIdx 和 newEndIdx 前移1位
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right 处理 头尾 同类型节点,即oldStartVnode和newEndVnode指向同类节点的情况
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // 把oldStartVnode的el节点元素右移到oldEndVnode的el节点后面紧跟节点元素的前面(说白了就是oldEndVnode的el节点的后面)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx] // 标记: oldStartIdx 后移1位, newEndIdx 前移1位
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left 处理 尾头 的同类型节点,即oldEndVnode和newStartVnode指向同类节点的情况
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // 把oldEndVnode的el节点左移到oldStartVnode的el节点的前面
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx] // 标记: oldEndIdx 前移1位 newStartIdx 后移1位
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // oldKeyToIdx一个对象,存储剩下oldCh的键值对: {key: 索引}
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 该三目是为了查找newStartVnode在oldCh的索引
if (isUndef(idxInOld)) { // New element undefined的话,说明newStartVnode未在oldCh中找到,说明它是一个新增的节点,则创建一个新的节点,且插入到oldStartVnode的elm dom的前面
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else { // 找到newStartVnode在oldCh中的索引,说明是需要处理更新的节点
vnodeToMove = oldCh[idxInOld] // 在oldCh中找到需要移动的vnode
if (sameVnode(vnodeToMove, newStartVnode)) { // 如果需要移动的vnode和newStartVnode是同类节点,则把vnodeToMove的el dom移动到oldStartVnode的el dom的前面,且把刚才移动的vnode标记为undefined
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined // 标记为undefined目的是:
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else { // 如果key相同,元素不同,则视为新增元素
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx] // 如果是处理新增节点的情况: oldCh中没有新增节点,所以标记过程中它的指针不需要移动,只需要把newStartIdx后移1位
// 如果是处理更新的节点的情况: 在oldCh中该节点不在指针处,所以采用设置为undefined的方式来标记,但是newCh中的newStartIdx后移1位
}
}
if (oldStartIdx > oldEndIdx) { // 当oldCh中的起止点相遇了,但是新vnode中的起止点没有相遇,这时需要对新vnode中的未处理节点进行处理,这类节点属于更新中被加入的节点,需要将他们插入到DOM树中
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) { // 当新vnode中的起止点相遇,且newStartIdx超过newEndIdx,需要把oldCh中oldStartIdx和oldEndIdx之间(包含他们)的dom删除(但是oldCh还有undefined标记的则不需要删除)
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}

配合以下demo来看这段代码

const res = Vue.compile(`
<div>
<p
v-for="(item, index) in arr"
:key="item"
>
{{ item }}
</p>
</div>
`) const vm = new Vue({
data: {
arr: Array.apply(null, { length: 10 }).map((item, index) => {
return index + 1
})
},
methods: {
switchArr() {
this.arr = [1, 9, 11, 7, 3, 4, 5, 6, 2, 10]
}
},
render: res.render,
staticRenderFns: res.staticRenderFns
}).$mount('#app')

1.处理头部相同的节点

else if (sameVnode(oldStartVnode, newStartVnode)) { // 处理 头部 的同类型节点,即oldStartVnode和newStartVnode指向同类节点的情况
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx] // 标记: oldStartIdx 和 newStartIdx 后移1位
}

2.处理 尾部 的同类型节点

else if (sameVnode(oldEndVnode, newEndVnode)) {  // 处理 尾部 的同类型节点,即oldEndVnode和newEndVnode指向同类节点的情况
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx] // 标记: oldEndIdx 和 newEndIdx 前移1位
}

3.处理 头尾 同类型节点

else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right 处理 头尾 同类型节点,即oldStartVnode和newEndVnode指向同类节点的情况
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // 把oldStartVnode的el节点元素右移到oldEndVnode的el节点后面紧跟节点元素的前面(说白了就是oldEndVnode的el节点的后面)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx] // 标记: oldStartIdx 后移1位, newEndIdx 前移1位
}

4.处理 尾头 的同类型节点

else if (sameVnode(oldEndVnode, newStartVnode)) {  // Vnode moved left 处理 尾头 的同类型节点,即oldEndVnode和newStartVnode指向同类节点的情况
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // 把oldEndVnode的el节点左移到oldStartVnode的el节点的前面
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx] // 标记: oldEndIdx 前移1位 newStartIdx 后移1位
}

5. 处理新增节点

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // oldKeyToIdx一个对象,存储剩下oldCh的键值对: {key: 索引}
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 该三目是为了查找newStartVnode在oldCh的索引
if (isUndef(idxInOld)) { // New element undefined的话,说明newStartVnode未在oldCh中找到,说明它是一个新增的节点,则创建一个新的节点,且插入到oldStartVnode的elm dom的前面
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}

6.处理需要更新的节点

if (isUndef(idxInOld)) { // New element undefined的话,说明newStartVnode未在oldCh中找到,说明它是一个新增的节点,则创建一个新的节点,且插入到oldStartVnode的elm dom的前面
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else { // 找到newStartVnode在oldCh中的索引,说明是需要处理更新的节点
vnodeToMove = oldCh[idxInOld] // 在oldCh中找到需要移动的vnode
if (sameVnode(vnodeToMove, newStartVnode)) { // 如果需要移动的vnode和newStartVnode是同类节点,则把vnodeToMove的el dom移动到oldStartVnode的el dom的前面,且把刚才移动的vnode标记为undefined
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined // 标记为undefined目的是:
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else { // 如果key相同,元素不同,则视为新增元素
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx] // 如果是处理新增节点的情况: oldCh中没有新增节点,所以标记过程中它的指针不需要移动,只需要把newStartIdx后移1位
// 如果是处理更新的节点的情况: 在oldCh中该节点不在指针处,所以采用设置为undefined的方式来标记,但是newCh中的newStartIdx后移1位

7.继续处理头部相同的节点

8.处理oldCh中未处理的节点删除

if (oldStartIdx > oldEndIdx) { // 当oldCh中的起止点相遇了,但是新vnode中的起止点没有相遇,这时需要对新vnode中的未处理节点进行处理,这类节点属于更新中被加入的节点,需要将他们插入到DOM树中
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) { // 当新vnode中的起止点相遇,且newStartIdx超过newEndIdx,需要把oldCh中oldStartIdx和oldEndIdx之间(包含他们)的dom删除(但是oldCh还有undefined标记的则不需要删除)
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}

至此diff算法结束了。

vue diff 算法学习的更多相关文章

  1. vue diff算法 patch

    1.diff比较算法 图示: diff比较只会在同层级进行, 不会跨层级比较. 所以diff是:广度优先算法. 时间复杂度:O(n) 代码示例: <!-- 之前 --> <div&g ...

  2. Vue diff 算法

    一.虚拟 DOM (virtual dom) diff 算法首先要明确一个概念就是 diff 的对象是虚拟DOM(virtual dom),更新真实 DOM 是 diff 算法的结果. 注:virtu ...

  3. vue是如何通过diff算法做渲染更新

    渲染页面 图中框起来的部分,vue会根据响应式变化的通知生成一颗新的 Virtual Dom Tree,然后将新的Virtual Dom Tree 和之前的 Virtual Dom Tree 做 di ...

  4. 详解vue的diff算法原理

    我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时,vue ...

  5. vue虚拟dom和diff算法

    vue的虚拟dom和diff算法 1.虚拟dom 虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的cla ...

  6. Vue源码终笔-VNode更新与diff算法初探

    写完这个就差不多了,准备干新项目了. 确实挺不擅长写东西,感觉都是罗列代码写点注释的感觉,这篇就简单阐述一下数据变动时DOM是如何更新的,主要讲解下其中的diff算法. 先来个正常的html模板: & ...

  7. 详解vue的diff算法

    前言 我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时, ...

  8. Vue 中 diff 算法后更新 DOM 的方法

    vue 2.0加入了 virtual dom,在 node_modules\vue\src\core\vdom\patch.js 中会对虚拟 DOM 进行 diff 算法等,然后更新 DOM. 网上的 ...

  9. vue的diff算法

    前言 我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时, ...

随机推荐

  1. leetcode — reverse-linked-list

    /** * Source : https://leetcode.com/problems/reverse-linked-list/ * * * Reverse a singly linked list ...

  2. SpringBoot整合系列-整合SpringMVC

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9984607.html SpringBoot整合Spring MVC 步骤 第一步:添加必 ...

  3. xamarin.forms之实现ListView列表倒计时

    做商城类APP时经常会遇到抢购倒计时的功能,之前做小区宝iOS的时候也有类似的功能,想着参考iOS做的思路,自定义一个Cell,在Cell中每秒刷新一下控件的文本值,但使用xamarin.forms实 ...

  4. C# 常用小点

    1]创建文件夹 //相对路径 string FilePath = Server.MapPath("./") + "ImageFile/Images/" + Da ...

  5. JavaScript是如何工作的: CSS 和 JS 动画底层原理及如何优化它们的性能

    摘要: 理解浏览器渲染. 原文:JavaScript是如何工作的: CSS 和 JS 动画底层原理及如何优化它们的性能 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这是专门探索 J ...

  6. 20190322-a标签、img标签、三列表、特殊字符实体、表格

    目录 1.a标签 a标签的属性 锚点 2.img标签 img标签的属性 图像热区 3.三列表 有序列表(Ordered List)     ol>li 无序列表(Unordered List)  ...

  7. 如何搭建一个VUE项目

    搭建环境 搭建node环境 下载 1.进入node.js官方网站下载页,点击下图中框出位置,进行下载即可,当前版本为8.9.4,下载网址为:https://nodejs.org/zh-cn/downl ...

  8. Python进阶之面向对象编程

    面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...

  9. 关于Keychain

    1.Keychain 浅析 2.iOS的密码管理系统 Keychain的介绍和使用 3.iOS开发中,唯一标识的解决方案之keyChain+UUID

  10. MySQL分数排名同分并列与不并列查询

    Scores表 | Id | Score | | 3.50 | | 3.65 | | 4.00 | | 3.85 | | 4.00 | | 3.65 | 并列 | Score | Rank | | | ...