The DOM-Diff Vue

VNode

Vue VNode type used to save virtual dom, Vue corresponding to the source code /* src/core/vdom/vnode.js */file definition. Different types of nodes can be described by a set of properties of collocation

    this.tag = tag  /* el标签名 */
    this.data = data  /* 存储特性信息 */
    this.children = children  /*当前节点的子节点,是一个数组*/
    this.text = text    /*  当前节点的文本  */
    this.elm = elm /*当前虚拟节点对应的真实dom节点*/
    this.ns = undefined
    this.context = context // 当前节点对应的Vue实例
    this.fnContext = undefined  /* 函数组件对应的option实例 */
    this.fnOptions = undefined /* 函数式组件对应的option选项  */
    this.fnScopeId = undefined 
    this.key = data && data.key  /*节点的key属性,被当作节点的标志,用以优化*/
    this.componentOptions = componentOptions /* 组件的option选项 */
    this.componentInstance = undefined /*当前节点对应的组件的实例*/
    this.parent = undefined /* 当前节点父节点 */
    this.raw = false
    this.isStatic = false /*  是否为静态节点  类似<p>静态节点,不包括数据</p>*/ 
    this.isRootInsert = true
    this.isComment = false /* 是否为注释节点 */
    this.isCloned = false
    this.isOnce = false /* 是否具有v-once 指令*/
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false

For example, to create a comment node

//  创建注释节点
export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  // 注释描述
  node.text = text
  // 是注释
  node.isComment = true
  return node
}

DOM-DIff

DOM-Diff is actually a comparison of old and new VNode of course, were the old VNode add nodes, delete nodes, the nodes update process. This process seems to refer to the process VNode new patch (patch) of the old VNode, the procedure defined in the source code src\core\vdom\patch.jsin.

  1. Old and new nodes are static nodes (similar <p>不存在</p>) and the same key to exit
    // 新节点和就节点都是静态节点则退出
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }
  1. If the new VNode no text attributes
    1. Two nodes have child nodes of the child node and unequal update
    2. New VNode has child nodes, the old VNode no children, compared with the old VNode add nodes
    3. Old VNode have child nodes, new VNode not, then delete the old VNode child nodes
    4. Does not meet the above, and the old VNode the text, then delete the old text VNode
 // 有没有text属性
    if (isUndef(vnode.text)) {
      // 如果两个都有子节点
      if (isDef(oldCh) && isDef(ch)) {
        // 如果不相等则更新子节点
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
        
      } 
      // 旧节点没有子节点,新节点有
      else if (isDef(ch)) {
        if (process.env.NODE_ENV !== 'production') {
          checkDuplicateKeys(ch)
        }
        // 判断旧节点是否有文本,如果有文本则先清空
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      }
      // 新节点没有子节点,旧节点有子节点
       else if (isDef(oldCh)) {
        //  直接移除
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } 
      // 新旧节点都没有子节点,但是子节点存在文本
      else if (isDef(oldVnode.text)) {
        // 清空文本节点
        nodeOps.setTextContent(elm, '')
      }
    } 
  1. If there is a new VNode text attributes, even more old and new VNode text for the new VNode text
    // 旧节点的文本不等于新节点
    else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }

Updating child nodes

The above DOM-Diff is actually very good understanding, place may be a little more difficult to deal with it is when new, old VNode have child nodes need to traverse the new, old VNode child nodes to be updated.

  // 如果两个都有子节点
      if (isDef(oldCh) && isDef(ch)) {
        // 如果不相等则更新子节点
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)   
      } 

Update strategy that is the outer loop newChildren, the inner loop oldChildren. The main loop when so few cases

  1. For the first time found the same node node patch update
  2. It found to be identical but different from the node positions, and the node will be moved to the patch before all outstanding nodes, not after having been moved to a processing node, if the node exists because added before, this should be moved to the current node after the new node before, prior to the new location where it will occupy after the node if moved to the processing nodes
  3. If the previous corresponding node is not found in oldChildren, then add a node to all outstanding nodes
  4. If traversed newChildren, oldChildren there is a node, then delete the remaining nodes
 if (isUndef(idxInOld)) {   // 如果在oldChildren里找不到当前循环的newChildren里的子节点
          // 那么久创建子节点并插入到合适位置
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        }
        // 在old中找到newchild子节点 
        else {
          // 需要移动的子节点
          vnodeToMove = oldCh[idxInOld]
          // 如果两个子节点相同
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // 更新子节点
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            // canmove表示需要移动节点
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } 
          else {
            // 相同接key,但是是不同的元素
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }

Optimization update the child node

Loop through the child nodes newer method, even though it can meet the requirements, but if this is a direct cycle, then the amount of the child node data relatively long time, the time complexity will be high, then you need to update the child node to optimize the use of new front newStart new post newEnd, old before oldStart, after oldEnd before the old two-way comparison, the comparison between any two
Here Insert Picture Description

  1. If the same comparison before the new update and the patch before the old, while the new and old former index before the index increase
  2. After the new and old are equal patch updates, while the old index and the new reduction
  3. The same before and after the new patch to update the old, before moving node to all outstanding nodes, while the new index before the increase, after the old index decrease
  4. After the new patch is the same as before the update and old, after the mobile node to all the nodes untreated, while the new index reduction, before the old index by
  5. If this is not carried out above the ordinary cycle update
  6. If oldStartIdx Index> oldEndIdx index represents newChildren node has not yet completed the cycle, is added (newStartIdx, newEndIdx) End range of the remaining nodes in the DOM
  7. If newStartIdx Index> newEndIdx index, there is no oldChildren traversed, then remove the (oldStartIdx, oldEndIdx) range node

It should be noted that the update on all the nodes are carried out for the old VNode, that is to say in front of the nodes of the patch process

    // 新前、新后,旧前、旧后两两更新
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 如果子节点没有,就下一个
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      }
      // 如果end不存在 则向前 
      else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      }
      // 如果新前节点与旧前节点相同,则更新,并同时向后移 
      else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      }
      // 如果旧后与新后相同,则更新,并同时向前移
      else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      }
      // 如果旧前与新后相同则更新并移动 
      else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      }
      // 如果旧后与新前相同,则更新并移动 
      else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      }
      //  如果不是上述情况将就进行普通的循环对比
      else {
        // 如果在oldChildren里找不到当前循环的newChildren里的子节点
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) {   // 如果在oldChildren里找不到当前循环的newChildren里的子节点
          // 那么久创建子节点并插入到合适位置
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        }
        // 在old中找到newchild子节点 
        else {
          // 需要移动的子节点
          vnodeToMove = oldCh[idxInOld]
          // 如果两个子节点相同
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // 更新子节点
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            // canmove表示需要移动节点
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } 
          else {
            // 相同接key,但是是不同的元素
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    // old循环完,new没循环完,将newStart -> newEnd 中间的值插入到DOM中
    if (oldStartIdx > oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    }
    // 如果新节点遍历完,old还没有遍历完,将oldStart -> oldEnd 的DOM移除
     else if (newStartIdx > newEndIdx) {
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
Published 85 original articles · won praise 62 · views 20000 +

Guess you like

Origin blog.csdn.net/qq_36754767/article/details/104374510