[Algorithm] Diff algorithm

Diff three strategies
The function of the Diff algorithm is to calculate the changed part of the Virtual DOM, and then perform native DOM operations on this part without re-rendering the entire page.
The Diff algorithm has three strategies:

  • Tree Diff
  • Component Diff
  • Element Diff

The execution order of the three strategies is also executed sequentially.
Tree Diff traverses each layer of the tree to find the difference, as shown in Figure 1. Tree Diff
Component Diff is a difference comparison at the data level

  1. If they are all components of the same type (ie: two nodes are two different instances of the same component class, for example:
    versus
    ), continue to compare the Virtual DOM tree according to the original strategy
  2. If there is a component that is not the same type, the component is judged as a dirty component, so as to replace all the child nodes under the entire component

Element Diff real DOM rendering, comparison of structural differences

First compare the first layer, the first layer is all R, no change; then enter the second layer Component Diff, find that the A component is not, delete A and its sub-components B, C; finally compare the third layer, create A And its subcomponents B and C.

When the nodes are at the same level, Diff provides three DOM operations: delete, move, and insert .

Element Diff
As shown in Figure 2, the first and last positions of OldVnode and NewVnode are marked as oldS, oldE, newS, and newE, respectively.

(1) oldS and newS are the same, no change, oldS++, newS++.

oldS = a,oldE = d
newS = a, newE = c

(2) NewS does not match OldVnode, insert f, newS++ before oldS.

oldS = b,oldE = d
newS = f, newE = c

(3) newS is the same as oldE, oldE moves to the front of oldS, newS++, oldE–.

oldS = b,oldE = d
newS = d, newE = c

(4) newE is the same as oldE, no change occurs, newE–, oldE–.

oldS = b,oldE = c
newS = e, newE = c

(5) Not the same, insert newE before oldS, delete oldS, oldS++, newS++, newE–, oldE–.

oldS = b,oldE = b
newS = e, newE = e

Finally, attach the core source code analysis:
patch

function patch (oldVnode, vnode) {
    
    
    // some code
    if (sameVnode(oldVnode, vnode)) {
    
    
        patchVnode(oldVnode, vnode)
    } else {
    
    
        const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点
        let parentEle = api.parentNode(oEl)  // 父元素
        createEle(vnode)  // 根据Vnode生成新元素
        if (parentEle !== null) {
    
    
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
            api.removeChild(parentEle, oldVnode.el)  // 移除以前的旧元素节点
            oldVnode = null
        }
    }
    // some code 
    return vnode
}

patchVnode (oldVnode, vnode) {
    
    
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
    
    
        api.setTextContent(el, vnode.text)
    }else {
    
    
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
    
    
            updateChildren(el, oldCh, ch)
        }else if (ch){
    
    
            createEle(vnode) //create el's children dom
        }else if (oldCh){
    
    
            api.removeChildren(el)
        }
    }
}

This function does the following:

  • Find the corresponding real dom, called el
  • Judge whether Vnode and oldVnode point to the same object, if so, return directly
  • If they both have text nodes and are not equal, then set the text node of el to the text node of Vnode.
  • If oldVnode has child nodes but Vnode does not, delete the child nodes of el
  • If oldVnode has no child nodes but Vnode does, then add the child nodes of Vnode to el after realizing
  • If both have child nodes, execute updateChildren function to compare child nodes

updateChildren

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    
    
    var oldStartIdx = 0;
    var newStartIdx = 0;
    var oldEndIdx = oldCh.length - 1;
    var oldStartVnode = oldCh[0];
    var oldEndVnode = oldCh[oldEndIdx];
    var newEndIdx = newCh.length - 1;
    var newStartVnode = newCh[0];
    var newEndVnode = newCh[newEndIdx];
    var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

    var canMove = !removeOnly;
    {
    
    
        checkDuplicateKeys(newCh);
    }
    // oldVnode起始位置小于结束位置并且newVnode起始位置小于结束位置
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    
    
        // isUndef 用来判断对象是否等于undefined或者为空,是的话返回true
        if (isUndef(oldStartVnode)) {
    
    
            // oldVnode 起始位置oldS++
            oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
        } else if (isUndef(oldEndVnode)) {
    
    
            // oldVnode 结束位置oldE--
            oldEndVnode = oldCh[--oldEndIdx];
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
    
    
            // oldS和newS相同,不变化,进行patch,oldS++,newS++
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
            oldStartVnode = oldCh[++oldStartIdx];
            newStartVnode = newCh[++newStartIdx];
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
    
    
            // oldE和newE相同,不变化,进行patch,oldE--,newE--
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
            oldEndVnode = oldCh[--oldEndIdx];
            newEndVnode = newCh[--newEndIdx];
        } else if (sameVnode(oldStartVnode, newEndVnode)) {
    
     // Vnode moved right
            // oldS和newE相同,oldS移动到oldE之后,进行patch,oldS++,newE--
            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
            // oldE和newS相同,oldE移动到oldS之前,进行patch,oldE--,newS++
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
            oldEndVnode = oldCh[--oldEndIdx];
            newStartVnode = newCh[++newStartIdx];
        } else {
    
    
            // 全都不相同情况下
            // 获取oldVnode->index的key
            if (isUndef(oldKeyToIdx)) {
    
    
                oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); 
            }
            idxInOld = isDef(newStartVnode.key)
              ? oldKeyToIdx[newStartVnode.key]
              : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
            if (isUndef(idxInOld)) {
    
     // New element
                // oldVnode->index为undefined或null,说明没有该元素,创建新的元素
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
            } else {
    
    
                  // 获取oldVnode
                  vnodeToMove = oldCh[idxInOld];
                  if (sameVnode(vnodeToMove, newStartVnode)) {
    
    
                      // 创建的Vnode和newS相同,插入到oldS之前,进行patch
                      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
                      oldCh[idxInOld] = undefined;
                      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
                  } else {
    
    
                      // 相同的key但是不一样的element. 被视为新的element
                      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
                  }
            }
            newStartVnode = newCh[++newStartIdx];
        }
    }
    // 当oldS>oldE时,将newS至newE间的全部插入
    if (oldStartIdx > oldEndIdx) {
    
    
        refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
        addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
    } else if (newStartIdx > newEndIdx) {
    
    
        // 当newS>newE,将oldS至oldE间的全部删除
        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
    }
}

Guess you like

Origin blog.csdn.net/u013034585/article/details/106128563