vue diff 算法学习
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 算法学习的更多相关文章
- vue diff算法 patch
1.diff比较算法 图示: diff比较只会在同层级进行, 不会跨层级比较. 所以diff是:广度优先算法. 时间复杂度:O(n) 代码示例: <!-- 之前 --> <div&g ...
- Vue diff 算法
一.虚拟 DOM (virtual dom) diff 算法首先要明确一个概念就是 diff 的对象是虚拟DOM(virtual dom),更新真实 DOM 是 diff 算法的结果. 注:virtu ...
- vue是如何通过diff算法做渲染更新
渲染页面 图中框起来的部分,vue会根据响应式变化的通知生成一颗新的 Virtual Dom Tree,然后将新的Virtual Dom Tree 和之前的 Virtual Dom Tree 做 di ...
- 详解vue的diff算法原理
我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时,vue ...
- vue虚拟dom和diff算法
vue的虚拟dom和diff算法 1.虚拟dom 虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的cla ...
- Vue源码终笔-VNode更新与diff算法初探
写完这个就差不多了,准备干新项目了. 确实挺不擅长写东西,感觉都是罗列代码写点注释的感觉,这篇就简单阐述一下数据变动时DOM是如何更新的,主要讲解下其中的diff算法. 先来个正常的html模板: & ...
- 详解vue的diff算法
前言 我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时, ...
- Vue 中 diff 算法后更新 DOM 的方法
vue 2.0加入了 virtual dom,在 node_modules\vue\src\core\vdom\patch.js 中会对虚拟 DOM 进行 diff 算法等,然后更新 DOM. 网上的 ...
- vue的diff算法
前言 我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时, ...
随机推荐
- J2EE规范总结
概述 J2ee是我们步入java学习的一个開始.它将开启这趟奇幻之旅,Java是一种简单的,跨平台的,面向对象的,分布式的.解释的.健壮的安全的.结构的中立的,可移植的.性能非常优异的多线程的,动态的 ...
- Docker多主机管理(八)--技术流ken
docker多主机管理 前面我们的实验环境中只有一个 docker host,所有的容器都是运行在这一个 host 上的.但在真正的环境中会有多个 host,容器在这些 host 中启动.运行.停止和 ...
- 用C#写的一个OA类的APP, ios、Android都能跑,有源代码
这是一个用C#写的OA类APP,功能包含请假.报销.部门管理.签到.IM.文件上传等功能 话不多说,先看视频 视频地址:http://v.youku.com/v_show/id_XMzUwMjQ1Mz ...
- [心得] SQL Server Partition(表分區) 資料分佈探討
最近在群裡有個朋友問了個問題是這樣的 用户表有一千多万行,主键是用户ID,我做了分区.但经常查询时,其它的表根据用户ID来关联,这样跨区查询,reads非常高.有什么好的处理办法?不分区的话,索引维护 ...
- C# 如何添加Excel页眉页脚(图片、文字、奇偶页不同)
简介 我们可以通过代码编程来对Excel工作表实现很多操作,在下面的示例中,将介绍如何来添加Excel页眉.页脚.在页眉处,我们可以添加文字,如公司名称.页码.工作表名.日期等,也可以添加图片,如LO ...
- 使用 CODING 进行 Hexo 项目的持续集成
本文作者:CODING 用户 - 廖石荣 关于持续集成的概念 持续集成指的是,频繁地(一天多次)将代码集成到主干. 持续集成的过程 如图所示: CI 过程:代码编写 -> 源代码库(GitHub ...
- Python进阶之函数式编程
函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...
- 如何在MongoDB设计存储你的数据(JSON化)?
第一步 定义要描述的数据集 当我们决定将数据存储下来的时候,我们首先要回答的一个问题就是:“我打算存储什么样的数据?这些数据之间有什么关系?实体之间有什么关系?实体的属性之间有什么关系”. 为了说明问 ...
- ASP.NET Zero--前期要求
前期要求 需要以下工具才能使用ASP.NET Zero Core解决方案: Visual Studio 2017 + Visual Studio扩展: Bundler&Minifier Web ...
- 【腾讯云服务器】基于centos7搭建ftp服务器(vsftpd)
该博客分为三部分设置,1.ftp服务器搭建.2.防火墙设置 3.腾讯云安全组 一.ftp服务器搭建 1.1 安装vsftpd yum install vsftpd -y 1.2 启动vsftpd服 ...