I. Implementation process

Diff algorithm node comparison criterion

  • The diff algorithm makes a fine comparison between the new virtual DOM and the old virtual DOM and finally re-renders the comparison result to reflect the real DOM

  • The DIff algorithm performs fine comparison only for the same virtual DOM, otherwise only violent inserts and inserts will be performed

  • The DIff algorithm will only compare between layers and not across layers

3. Optimization strategy of DIff algorithm

Four Pointers:

  • OldStartIdx (old front pointer)

  • NewStartIdx (new front pointer)

  • OldEndIdx (old after pointer)

  • NewEndIdx (new post-pointer)

The Map cache

4. Matching search rules for diff algorithm Pointers

4.1 Egress failed to match:

oldStartIdx>oldEndIdx && newStartIdx>newEndIdx
Copy the code

4.2 Matching Search rules

① The new front and the old front ② the new back and the old back ③ the new back and the old front ④ The new front and the old back in order to hit and hit only one if all four are not hit then re-execute the cycle

4.3 Match Details

4.3.1 New and old

Old front -> A A <- new front B B old back -> C M N C <- new backCopy the code

When the new front and the old front hit, the new front and the old front continue to move down

A A B B old before -> old after -> C M <- new before N C <- new afterCopy the code

When the new front pointer and the old back pointer do not match, judge whether the new back pointer and the old back pointer match if the two Pointers move up

A A old back -> B B old front -> C M <- new front N <- new back CCopy the code

The loop ends with oldStartIdx>oldEndIdx, and we find that the elements in the preceding and trailing Pointers are the ones we need to add

New rule: We want to insert the element (M,N) before the old pointer (C).

4.3.2 New and old

Old front -> A C <- new front B D <- new rear C old rear -> DCopy the code

When the old and new front Pointers do not match, judge whether the new and old rear Pointers match, then move the pointer up

Old front -> A C <- new front <- new rear B D old rear -> C DCopy the code

When the old and new front Pointers do not match, judge whether the new and old rear Pointers match, then move the pointer up

<- new - old -> A C <- new - old -> B D C DCopy the code

The newStartIdx>newEndIdx loop ends, and we find that the elements contained in the old before and after Pointers are the elements we need to delete, so we iterate over the pointer to delete those two elements

4.3.3 New and old

Old front -> A C <- new front B B old back -> C A <- new backCopy the code

When the old front doesn’t match the new front and the old back doesn’t match the new back, we try to match the old front and the new back, after the hit we insert the old front into the old back and move the pointer

C <- new old -> B B <- new old -> C A ACopy the code

When the old front doesn’t match the new front and the old back doesn’t match the new back, we try to match the old front and the new back, after the hit we insert the old front into the old back and move the pointer

C <- new <- new after B Old before -> old after -> C A B ACopy the code

Both sides of the old and new hit move pointer end the loop

4.4.4 New before and old after

Old before -> A C <- new before B A old after -> C B <- new afterCopy the code

The current three hits all fail, but with the new before and the old after fourth hit, we insert the old after node before the old before and move the pointer accordingly.

C Old front -> A C Old back -> B A <- new front B <- new backCopy the code

Then the subsequent hits end, and the loop ends on both sides.

4.5 None of the four modes are hit

When all four ways without hitting the need for the operation, in order to optimize the performance of our use of the Map of all the old node index, if the new node is new, we can't find it in the old node corresponding index need to insert the new node, if the new node can be found in the old node corresponding index is to move the node, and needs to be Moves the new front pointer backward to end the loop and remove the excess elements

The old front -> A D <- new front new rear B old rear -> CCopy the code

We can see that in this case none of the four are hit. We need to insert D in front of the old pointer and move the new pointer to end the loop and delete the old pointer to the ABC element covered by the old pointer

Old before -> A B <- new before new after B old after -> CCopy the code

In this case, we need to insert the element in the new node in front of the old node, move the pointer to end the loop, and delete the element ABC covered by the old pointer and the old pointer.

4.6 summarize

The above analysis has been very detailed, we only need to implement the diff algorithm according to the above six cases of code, from the above six cases, we can insert elements in three ways, and delete elements in one way.

5. Write diff according to the above analysis (may differ from the source)

function checkSameVnode(a,b){
  return a.sel == b.sel && a.key == b.key  // Check whether it is the same virtual node
}

export default function updateChildren(parentElm,oldCh,newCh){
  let oldStartIdx = 0 / / the old before
  let newStartIdx = 0 / / new front
  let oldEndIdx = oldCh.length-1 / / after the old
  let newEndIdx = newCh.length-1 / / new after
  let oldStartVnode = oldCh[0] // Old former node
  let oldEndVnode = oldCh[oldEndIdx] // Old back node
  let newStartVnode = newCh[0]  // New front node
  let newEndVnode = newCh[newEndIdx] // New post-node
  let keyMap = null;

  while(oldStartIdx<=oldEndIdx && newStartIdx <= newEndIdx){
    if(oldStartVnode == null || oldStartVnode === undefined){
      oldStartVnode = oldCh[++oldStartIdx]
    }else if(oldEndVnode == null oldEndVnode === undefined){
      oldEndVnode = oldCh[--oldEndIdx]
    }else if(newStartVnode == null newStartVnode === undefined){
      newStartVnode = newCh[++newStartIdx]
    }else if(newEndVnode == null || newEndVnode === undefined){
      newEndVnode = newCh[--newEndIdx]
    }else if(checkSameVnode(oldStartVnode,newStartVnode)){
      // Make a comparison between the old and the new to determine whether to update
      patchVnode(oldStartVnode,newStartVnode)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    }else if(checkSameVnode(oldEndVnode,newEndVnode)){
      // New queen and old queen
      patchVnode(oldEndVnode,newEndVnode)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    }else if(checkSameVnode(oldStartVnode,newEndVnode)){
      // The new and the old
      // Move the new node to the old node
      patchVnode(oldStartVnode,newEndVnode)
      parentElm.insertBefore(oldStartVnode.elm,oldEndVnode.elm.nextSibling)
      oldStartVnode = oldCh[++oldStartIdx]
      newEndVnode = newCh[--newEndIdx]
    }else if(checkSameVnode(newStartVnode,oldEndVnode)){
      // New before and old after
      // The new front node needs to be moved to the front of the old one
      patchVnode(oldEndVnode,newStartVnode)
      parentElm.insertBefore(oldEndVnode.elm,oldStartVnode.elm)
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    }else{
      Using the map cache is equivalent to moving the node in a loop
      / / cache
      if(! keyMap){ keyMap = {}for(leti=oldStartIdx; i<=oldEndIdx; i++){const key = oldCh[i].key;
          if(key! =undefined){ keyMap[key] = i; }}}// Find the position of the unmatched item in the old node
      const idxInOld = keyMap[newStartVnode.key]
      if(idxInOld == undefined) {// Is a brand new item that needs to add and delete elements between oldStartIdx and oldEndIdx
        parentElm.insertBefore(createElement(newStartVnode),oldStartVnode.elm)
      }else{
        // Not entirely new items need to be moved and the element matched in oldCh is set to undefined before moving to the old and then also removing elements between oldStartIdx and oldEndIdx
        const elmToMove = oldCh[idxInOld];
        patchVnode(elmToMove,newStartVnode);
        // Keep this setting undefined
        oldCh[idxInOld] = undefined
        // Move to the old before
        parentElm.insertBefore(elmToMove.elm,oldStartVnode.elm)
      }
      newStartVnode = newCh[++newStartIdx]
      
    }
  }
  // After the loop ends
  if(newStartIdx <= newEndIdx){
    / / to be inserted
    // Insert before the unprocessed node
      const before = newCh[newEndIdx+1] = =null ? null : newCh[newEndIdx+1].elm;
    for(leti=newStartIdx; i<=newEndIdx; i++){ parentElm.insertBefore(createElement(newCh[i]),before) } }else if(oldStartIdx<=oldEndIdx){
    / / to be deleted
    for(leti = oldStartIdx; i<=oldEndIdx; i++){if(oldCh[i]){
        parentElm.removeChild(oldCh[i].elm)
      }
    }
  }
}

Copy the code