1

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内的文本即可

image.png

更新子节点updateChildren()

updateChildren主要作用是已一种高效的方式对比新旧vnode的children得出最小操作补丁,
image.png

在新老两组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即可
![image.png](/img/bVcHvaz)


如果oldStartVnode和newEndVnode满足sameVnode, 将该vnode节点进行patchVnode, 还需要将该真实节点移动到oldEndVnode后面
![image.png](/img/bVcHvaF)


如果oldEndVnode和newStartVnode满足sameVnode, 将该vnode节点进行patchVnode, 还需要将oldEndVnode对应的dom移动到oldStartVnode对应的dom前面
![image.png](/img/bVcHvaM)



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


如果newStartVnode在oldvnode节点中找不到一致的sameVnode, 会调用createElm创建一个新的节点.
![image.png](/img/bVcHvaS)



循环结束

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



当循环结束时newStartIdx > newEndIdx 说明新的vnode节点已经遍历完了,老的节点还有剩余, 需要执行删除操作
![image.png](/img/bVcHva2)





 
 
 
 
 
 
 
 
 
 
 
 
 
 

lolo
12 声望0 粉丝