function sameVnode (a, b) {
return (
a.key === b.key && // key值
a.tag === b.tag && // 标签名
a.isComment === b.isComment && // 是否为注释节点
// 是否都定义了data,data包含一些具体信息,例如onclick , style
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b) // 当标签是<input>的时候,type必须相同
)
}
function patchVnode(oldVnode, newVnode){
const el = newVnode.el = oldVnode.el //合并引用
let i, oldCh = oldVnode.children, ch = newVnode.children
if (oldVnode === newVnode) return //如果严格一致直接返回
if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) { // 文本节点处理
api.setTextContent(el, newVnode.text)
}else{
updateEle(el, newVnode, oldVnode) //更新节点
if (oldCh && ch && oldCh !== ch) {
updateChildren(el, oldCh, ch)
}else if (ch){
createEle(newVnode)
}else if (oldCh){
api.removeChildren(el)
}
}
}
有子节点 —>无子节点 无子节点 —>有子节点 子节点—>文本节点 文本节点—>子节点 这几种情况都比较简单,关键点在于有子节点—>有子节点这种情况的比较 为了方便理解可以虚拟成两个数组进行比较
function sameVnode (a, b) {//节点相同判断
return (
a.key === b.key && // key值
a.tag === b.tag && // 标签名
a.isComment === b.isComment && // 是否为注释节点
// 是否都定义了data,data包含一些具体信息,例如onclick , style
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b) // 当标签是<input>的时候,type必须相同
)
}
function patchVnode(oldVnode, newVnode){
const el = newVnode.el = oldVnode.el //合并引用
let i, oldCh = oldVnode.children, ch = newVnode.children
if (oldVnode === newVnode) return //如果严格一致直接返回
if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) { // 文本节点处理
api.setTextContent(el, newVnode.text)
}else{
updateEle(el, newVnode, oldVnode) //更新节点
if (oldCh && ch && oldCh !== ch) {
updateChildren(el, oldCh, ch)
}else if (ch){
createEle(newVnode)
}else if (oldCh){
api.removeChildren(el)
}
}
}
function updateChildren(el,newChidlren,OldChidlren){ //挂载父节点
let oldStartIdx = 0;//老节点的数组开始索引
let oldEndIdx = OldChidlren.length - 1; //老节点的数组结束索引
let newStartIdx = 0;//新节点的数组开始索引
let newEndIdx = newChidlren.length - 1; //新节点的数组结束索引
let oldStartVnode = OldChidlren[0] //老的开始节点
let oldEndVnode = OldChidlren[oldEndIdx]//老的结束节点
let newStartVnode = newChidlren[0]//新的开始节点
let newEndVnode = newChidlren[newEndIdx] //新的结束节点
while(oldStartIdx<=oldEndIdx && newStartIdx<=newEndIdx){//结束条件,两组数据都处理完
if (oldStartVnode == null) { // 处理空节点
oldStartVnode = OldChidlren[++oldStartIdx]
}else if (oldEndVnode == null) {
oldEndVnode = OldChidlren[--oldEndIdx]
}else if (newStartVnode == null) {
newStartVnode = newChidlren[++newStartIdx]
}else if (newEndVnode == null) {
newEndVnode = newChidlren[--newEndIdx]
//所有操作其实都是在老列表上进行,整个列表会被分割为已经操作过的节点和未操作的节点两部分
//操作过的节点组成最后结果列表,未操作过的列表继续作为旧列表进行匹配操作
}else if(sameVnode(newStartVnode,oldStartVnode )){//头头相等
patchVnode(newStartVnode,oldStartVnode) //直接合并,处理下一个
oldStartVnode = OldChidlren[++oldStartIdx]
newStartVnode = newChidlren[++newStartIdx]
}else if(sameVnode(newEndVnode,oldEndVnode )){ //尾尾相等
patchVnode(newEndVnode,oldEndVnode) //直接合并,处理上一个
oldStartVnode = OldChidlren[--oldEndIdx]
newStartVnode = newChidlren[--newEndIdx]
}else if(sameVnode(oldStartVnode, newEndVnode)){ //老头等于新尾
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el)) //则把老头移到最后 这里的头尾都是虚拟头尾,参照双指针区间,不一定是实际列表尾
oldStartVnode = OldChidlren[++oldStartIdx] //处理下一个老头
newEndVnode = newChidlren[--newEndIdx]//处理下一个新尾
}else if(sameVnode(oldEndVnode, newStartVnode)){ //老尾等于新头
patchVnode(oldEndVnode, newStartVnode)//则吧老尾移到最头
api.insertBefore(parentElm, oldEndVnode.el,oldStartVnode.el)
oldEndVnode = OldChidlren[--oldEndIdx] //处理上一个老尾
newStartVnode = newChidlren[++newStartIdx]//处理下一个新头
}else {//进行key值判断
if (oldKeyToIdx === undefined){
oldKeyToIdx = createKeyToOldIdx(OldChidlren, oldStartIdx, oldEndIdx) // 创建旧列表哈希表
}
idxInOld = oldKeyToIdx[newStartVnode.key] //在旧表中查询是否存在对应的节点
if(!idxInOld){//不存在符合项
api.insertBefore(parentElm,createEle(newStartVnode).el,oldStartVnode.el) //把新列表的头节点插入旧表最前面
newStartVnode = newChidlren[++newStartIdx]//继续处理下一个
}else{
elmToMove = oldChidlren[idxInOld]//获取对应的节点
if (elmToMove.sel !== newStartVnode.sel) {//如果为不为同一个DOM
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)//创建一个插入到头 这里是处理一些key值一样,但实际上还是不一样的节点
}else{
patchVnode(elmToMove, newStartVnode)//合并节点
oldChidlren[idxInOld] = null//旧表对应位置置空
api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)//把对应节点插入到旧表最头
}
newStartVnode = newChidlren[++newStartIdx]//继续处理下一个
}
if (oldStartIdx > oldEndIdx) {//旧表全处理完
before = newChidlren[newEndIdx + 1] == null ? null : newChidlren[newEndIdx + 1].el
addVnodes(parentElm, before, newChidlren, newStartIdx, newEndIdx)//把新表剩余的剩余元素都插入到旧表后
}else if (newStartIdx > newEndIdx) {//新表处理完
removeVnodes(parentElm, oldChidlren, oldStartIdx, oldEndIdx)//把旧表剩余的元素都删除
}
}
}
}