https://www.processon.com/vie...
响应式触发时,让页面的重新渲染这一过程高效完成.
diff算法做的事情是比较vnode和oldVnode, 再以VNode为标准的情况下在oldVnode做小的改动, 完成VNode对应的dom渲染
回到之前的_updated方法
Vue.prototype._update = function(vnode) {
const vm = this
const prevVnode = vm._vnode
vm._vnode = vnode // 缓存为之前vnode
if(!prevVnode) { // 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode)
} else { // 重新渲染
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
patch无非做三件事
创建新增节点
删除废弃节点
更新已有节点
创建新增节点
有两种情况
- VNode中有的节点,oldVNode没有,将VNode渲染为真实dom插入即可
- VNode和oldVNode不是同一个节点, 直接将VNode渲染为真实dom,插入到旧的节点后面, 并删除旧的节点
判断两个节点是否是同一个节点
function sameVnode (a, b) { // 是否是相同的VNode节点
return (
a.key === b.key && ( // 如平时v-for内写的key
(
a.tag === b.tag && // tag相同
a.isComment === b.isComment && // 注释节点
isDef(a.data) === isDef(b.data) && // 都有data属性
sameInputType(a, b) // 相同的input类型
) || (
isTrue(a.isAsyncPlaceholder) && // 是异步占位符节点
a.asyncFactory === b.asyncFactory && // 异步工厂方法
isUndef(b.asyncFactory.error)
)
)
)
}
删除废弃节点
比较vnode和oldvnode,如果根节点不相同就将VNode渲染为真实dom,插入到旧节点后面. 删除掉废除的旧节点
if (isDef(parentElm)) { // 在它们的父节点内删除旧节点
removeVnodes(parentElm, [oldVnode], 0, 0)
}
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
removeNode(ch.elm)
}
}
} // 移除从startIdx到endIdx之间的内容
------------------------------------------------------------
function removeNode(el) { // 单个节点移除
const parent = nodeOps.parentNode(el)
if(isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
更新已有节点
当两个节点是相同节点时, 需要找出来它们的不同之处. 使用patchVnode方法
都是静态节点
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key
) {
// 都是静态节点跳过
vnode.componentInstance=oldVnode.componentInstance
return
}
vnode节点没有文本属性
// 3. vnode没有文本属性
if (isUndef(vnode.text)) {
// 双方都有孩子节点: 比较子节点~~~~
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch)
// children不同 更新子节点
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 只有Vnode有子节点
if (isDef(oldVnode.text)) {
// oldnode有文本节点 设置oldvnode为空
nodeOps.setTextContent(elm, '')
}
// 往oldvnode空的标签内插入vnodechildren的真实dom
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 只有oldvnode有children 全部移除
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
// oldVnode有文本节点 设置为空~~~~
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
// 文本更新
nodeOps.setTextContent(elm, vnode.text)
}
如果vnode没有文本节点, 会有四个分支
- 都有children且不同,使用updateChildren方法更详细比对他们的children
- 只有vnode有children, 清空oldvnode, 并将vnode的children创建为真实dom插入oldvnode
- 只有oldvnode有children, 清空oldvnode
- 只有oldvnode有文本, 清空oldvnode
总结: 以vnode为标准, 所以vnode有文本节点的话, 无论oldvnode是什么类型节点, 直接设置未vnode内的文本即可
更新子节点updateChildren()
updateChildren主要作用是已一种高效的方式对比新旧vnode的children得出最小操作补丁,
在新老两组vnode节点的左右头尾两侧都有一个变量标记. 在遍历过程中这两个参数都会像中间靠拢.当oldStartIdx>oldEndIdx或者newStartIdx> newEndIdx时结束循环
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly){
let oldStartIdx = 0 // 旧vnode第一个下标
let newStartIdx = 0 // 新vnode第一个下标
let oldEndIdx = oldCh.length - 1 // 旧vnode最后一个下标
let oldStartVnode = oldCh[0] // 旧vnode第一个节点
let oldEndVnode = oldCh[oldEndIdx] // 旧vnode最后一个节点
let newEndIdx = newCh.length - 1 // 新vnode最后一个下标
let newStartVnode = newCh[0] // 新vnode第一个节点
let newEndVnode = newCh[newEndIdx] // 新vnode第一个节点
let oldKeyToIdx // 旧节点key和下标的对象集合
let idxInOld // 新节点key在旧节点key集合里的下标
let vnodeToMove // idxInOld对应的旧节点
let refElm // 参考节点
}
遍历规则:
首先将oldStartVnode oldEndVnode与新的newStartVnode newEndVnode两两交叉比较
当oldStartVnode和newStartVnode或者oldEndVnode和newEndVnode满足sameVnode.直接将该vnode节点进行patchVnode即可

如果oldStartVnode和newEndVnode满足sameVnode, 将该vnode节点进行patchVnode, 还需要将该真实节点移动到oldEndVnode后面

如果oldEndVnode和newStartVnode满足sameVnode, 将该vnode节点进行patchVnode, 还需要将oldEndVnode对应的dom移动到oldStartVnode对应的dom前面

如果以上情况均不符合,则拿newStartVnode去oldvnode里进行遍历查找相同节点, 若存在执行patchVnode, 同时将elmToMove移动到oldStartIdx对应的dom前面.

如果newStartVnode在oldvnode节点中找不到一致的sameVnode, 会调用createElm创建一个新的节点.

循环结束
当循环结束时oldStartIdx > oldEndIdx, 这时候旧的Vnode节点已经遍历完了,但是新的节点还没有, 表明新的vnode节点比旧的vnode节点多.
需要将剩下的vnode对应的dom插入到真实dom中,

当循环结束时newStartIdx > newEndIdx 说明新的vnode节点已经遍历完了,老的节点还有剩余, 需要执行删除操作

**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。