Simple version of Vue2 Diff algorithm

background

Review Vue2the double-ended comparison method~

double-ended comparison

The double-ended comparison method adopted by Vue2, that is, the head-to-tail comparison between the new list and the old list. During the comparison process, the pointer will gradually move inward until all the nodes of a certain list have been traversed, and the comparison stops.
for example:

# 旧列表
a b c d
# 新列表
d b a c

We will first define a few variables:

function vue2diff(prevChildren, nextChildren, parent) {
    
    
	let
    // 旧头指针 
    oldStartIndex = 0,
    // 新头指针
    newStartIndex = 0,
    // 旧尾指针
    oldEndIndex = prevChildren.length - 1,
    // 新尾指针
    newEndIndex = nextChildren.length - 1,
    // 旧头节点
    oldStartNode = prevChildren[oldStartIndex],
    // 旧尾节点
    oldEndNode = prevChildren[oldEndIndex],
    // 新头节点
    newStartNode = nextChildren[newEndIndex],
    // 新尾节点
    newEndNode = nextChildren[newStartIndex];
}

Then at this time we have four pointers and the nodes corresponding to the four pointers, which are node a of the old list, node d of the old list, node d of the new list, and node c of the new list. For the convenience of the following expression: the node a of the old list will be written as, and so on oldNodeA.

  1. oldStartNodeand newStartNodecontrast keyvalue ;
  2. oldEndNode and newEndNode contrast keyvalues ;
  3. oldStartNodeand newEndNode contrast keyvalues ;
  4. newStartNodeand oldEndNode comparison keyvalue .
    as the picture shows:
    insert image description here

Comparison process

Next, enter our comparison process~ The comparison process is mainly to find nodes with the same key value

Achieving two-ended comparisons

Let me first talk about the double-ended comparison, if you encounter the same node (passed key):

  • oldStartNodeWhen newStartNodethe keysum is the same, the sum oldStartIndexmoves newStartIndexbackward one bit at the same time;
  • oldEndNodeWhen newEndNodethe keysum is the same, the sum moves forward one bit at the same time oldEndIndex;newEndIndex
  • oldStartNodeWhen newEndNodethe keysum is the same, oldStartIndexmove one bit backward and newEndIndexone bit forward;
  • oldEndNodeWhen newStartNodethe keysum is the same, oldEndIndexmove forward one bit and newStartIndexmove backward one bit.

Conditions for ending the loop When all the nodes in one of the lists have been traversed, our comparison process is completed.

Take this as an example:

# 旧列表
a b c d
# 新列表
d b a c

When comparing for the first time:
insert image description here

  • oldStartNode A Different from newStartNode D, continue to compare;
  • oldEndNode DDifferent from newEndNode C, continue to compare;
  • oldEndNode Dis newStartNode Dthe same as , then the position of node D in the old list will be changed at this time, moved to the front of A, then oldEndIndexmoved forward and newStartIndexbackward.
    The following figure is obtained:
    insert image description here
    the result of the virtual DOM at this time is:
d a b c

Next, continue to compare:

  • oldStartNode A Different from newStartNode B, continue to compare;
  • - oldEndNode Cis newEndNode Cthe same as and, but they all belong to the tail node, so we directly reuse the node and move oldEndIndexboth and newEndIndexforward
    to get the following figure:
    insert image description here
    At this time, the result of the virtual DOM is:
d a b c

Next, continue to compare:

  • oldStartNode A Different from newStartNode B, continue to compare;
  • oldEndNode B Different from newEndNode A, continue to compare;
  • oldStartNode A And newEndNode A, just move the A node in the old list behind the B node, oldStartIndexmove one bit backward, and newEndIndexmove one bit forward
    to get the following picture:
    insert image description here
    At this time, the result of the virtual DOM is:
d b a c

Finally, it is consistent oldEndNode B with newEndNode Bthe comparison, and then ends the cycle ~
the general code is as follows:

function vue2diff(prevChildren, nextChildren, parent) {
    
    
  // ...
  // 双端对比,当有一个列表的节点全部遍历完成,则结束循环
  while (oldStartIndex <= oldStartIndex && newStartIndex <= newStartIndex) {
    
    
    if (oldStartNode.key === newStartNode.key) {
    
    
      // 当两个头节点相同时,节点不需要处理,两个指针的坐标向后移动一位
      patch(oldStartNode, newStartNode, parent)

      oldStartIndex++
      newStartIndex++
      oldStartNode = prevChildren[oldStartIndex]
      newStartNode = nextChildren[newStartIndex]
    } else if (oldEndNode.key === newEndNode.key) {
    
    
      // 当两个尾节点相同时,节点不需要处理,两个指针的坐标向前移动一位
      patch(oldEndNode, newEndNode, parent)

      oldEndIndex--
      newEndIndex--
      oldEndNode = prevChildren[oldEndIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldStartNode.key === newEndNode.key) {
    
    
      // 当旧头节点跟新尾节点相同时,需要移动旧头节点,旧指针向后移动一位,新指针向前移动一位
      patch(oldStartNode, newEndNode, parent)
      parent.insertBefore(oldStartNode.el, oldEndNode.el.nextSibling)
      oldStartIndex++
      newEndIndex--
      oldStartNode = prevChildren[oldStartIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldEndNode.key === newStartNode.key) {
    
    
      // 当旧尾节点和新头节点相同时,需要移动旧头节点,旧指针向前移动一位,新指针向后移动一位
      patch(oldEndNode, newStartNode, parent)
      parent.insertBefore(oldEndNode.el, oldStartNode.el)
      oldEndIndex--
      newStartIndex++
      oldEndNode = prevChildren[oldEndIndex]
      newStartNode = nextChildren[newStartIndex]
    } else {
    
    
      // ...
    }
  }
}

The above is the case where the same node will appear in the comparison of the old and new nodes at the head and tail. Next, let's look at the situation where no reuse node can be found in the four comparisons:
insert image description here

  • oldStartNode AnewStartNode Edifferent from
  • oldEndNode DnewEndNode Hdifferent from
  • oldStartNode AnewEndNode Hdifferent from
  • oldEndNodeD is newStartNode Edifferent from;

At this time, we will first take out the first node of the new list , and then find out whether there is a node that can be reused in the old list. There are two situations here. Let’s talk about the one on the picture first: if oldStartNode Ait does not exist, then it means that this is a new node, and it can be placed directly at the front of the old list ;

insert image description here

insert image description here

function vue2diff(prevChildren, nextChildren, parent) {
    
    
  // ...
  // 双端对比,当有一个列表的节点全部遍历完成,则结束循环
  while (oldStartIndex <= oldStartIndex && newStartIndex <= newStartIndex) {
    
    
    if (oldStartNode.key === newStartNode.key) {
    
    
      // 当两个头节点相同时,节点不需要处理,两个指针的坐标向后移动一位
      patch(oldStartNode, newStartNode, parent)

      oldStartIndex++
      newStartIndex++
      oldStartNode = prevChildren[oldStartIndex]
      newStartNode = nextChildren[newStartIndex]
    } else if (oldEndNode.key === newEndNode.key) {
    
    
      // 当两个尾节点相同时,节点不需要处理,两个指针的坐标向前移动一位
      patch(oldEndNode, newEndNode, parent)

      oldEndIndex--
      newEndIndex--
      oldEndNode = prevChildren[oldEndIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldStartNode.key === newEndNode.key) {
    
    
      // 当旧头节点跟新尾节点相同时,需要移动旧头节点,旧指针向后移动一位,新指针向前移动一位
      patch(oldStartNode, newEndNode, parent)
      parent.insertBefore(oldStartNode.el, oldEndNode.el.nextSibling)
      oldStartIndex++
      newEndIndex--
      oldStartNode = prevChildren[oldStartIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldEndNode.key === newStartNode.key) {
    
    
      // 当旧尾节点和新头节点相同时,需要移动旧头节点,旧指针向前移动一位,新指针向后移动一位
      patch(oldEndNode, newStartNode, parent)
      parent.insertBefore(oldEndNode.el, oldStartNode.el)
      oldEndIndex--
      newStartIndex++
      oldEndNode = prevChildren[oldEndIndex]
      newStartNode = nextChildren[newStartIndex]
    } else {
    
    
      // 四个节点对比的过程中没有发现相同的节点时
      // 先看看新列表的头节点是否存在于旧列表中
      let newKey = newStartNode.key,
        oldIndex = prevChildren.findIndex(child => child && (child.key === newKey));
      // 如果不存在,直接创建放在最前方即可
      if (oldIndex === -1) {
    
    
        mount(newStartNode, parent, oldStartNode.el)
      } else {
    
    
      // 如果存在,将旧节点移动到第一个节点,并在旧列表中置成undefine, 跳过对比过程
        let prevNode = prevChildren[oldIndex]
        patch(prevNode, newStartNode, parent)
        parent.insertBefore(prevNode.el, oldStartNode.el)
        prevChildren[oldIndex] = undefined
      }
      // 更新新列表的对比节点
      newStartIndex++
      newStartNode = nextChildren[newStartIndex]
    }
  }
}

The problem that needs to be considered at this time is that when entering the next cycle, the corresponding oldStartNode is undefined, so the code needs to be dealt with:

function vue2diff(prevChildren, nextChildren, parent) {
    
    
  let
    // 旧头指针 
    oldStartIndex = 0,
    // 新头指针
    newStartIndex = 0,
    // 旧尾指针
    oldEndIndex = prevChildren.length - 1,
    // 新尾指针
    newEndIndex = nextChildren.length - 1,
    // 旧头节点
    oldStartNode = prevChildren[oldStartIndex],
    // 旧尾节点
    oldEndNode = prevChildren[oldEndIndex],
    // 新头节点
    newStartNode = nextChildren[newEndIndex],
    // 新尾节点
    newEndNode = nextChildren[newStartIndex];
  // 双端对比,当有一个列表的节点全部遍历完成,则结束循环
  while (oldStartIndex <= oldStartIndex && newStartIndex <= newStartIndex) {
    
    
    // 旧列表中遇到Undefine节点则跳过对比
    if (oldStartNode === undefined) {
    
    
      oldStartNode = prevChildren[++oldStartIndex]
    } else if (oldEndNode === undefined) {
    
    
      oldEndNode = prevChildren[--oldStartIndex]
    } else if (oldStartNode.key === newStartNode.key) {
    
    
      // ...
    } else if (oldEndNode.key === newEndNode.key) {
    
    
      // ...
    } else if (oldStartNode.key === newEndNode.key) {
    
    
      // ...
    } else if (oldEndNode.key === newStartNode.key) {
    
    
      // ...
    } else {
    
    
      // ...
    }
  }
  
}

Next, let's consider the case of list deletion:
insert image description here

  • Node A multiplexing;
  • Node D is multiplexed;
    at this time, each coordinate changes as follows:
    insert image description here
// 当新列表的newStartIndex 大于newEndIndex,说明新列表删除的节点
  if (newStartIndex > newEndIndex) {
    
    
    while (oldStartIndex <= oldStartIndex) {
    
    
      if (!prevChildren[oldStartIndex]) {
    
    
        oldStartIndex++
        continue
      }
      // 删除节点
      parent.removeChild(prevChildren[oldStartIndex++].el)
    }
  } 

Finally, it is the case of adding nodes~
insert image description here

  • Node A multiplexing;
  • Node B is multiplexed;
    at this time, each coordinate changes as follows:
    insert image description here
if (oldStartIndex> oldEndIndex ) {
    
    
    // 当旧列表的oldEndIndex 小于oldStartIndex,说明新列表新增了节点
    for (let i = newStartIndex; i <= newEndIndex; i++) {
    
    
      mount(nextChildren[i], parent, prevStartNode.el)
    }
  }

Finally, paste the complete code:

function vue2diff(prevChildren, nextChildren, parent) {
    
    
  let
    // 旧头指针 
    oldStartIndex = 0,
    // 新头指针
    newStartIndex = 0,
    // 旧尾指针
    oldEndIndex = prevChildren.length - 1,
    // 新尾指针
    newEndIndex = nextChildren.length - 1,
    // 旧头节点
    oldStartNode = prevChildren[oldStartIndex],
    // 旧尾节点
    oldEndNode = prevChildren[oldEndIndex],
    // 新头节点
    newStartNode = nextChildren[newEndIndex],
    // 新尾节点
    newEndNode = nextChildren[newStartIndex];
  // 双端对比,当有一个列表的节点全部遍历完成,则结束循环
  while (oldStartIndex <= oldStartIndex && newStartIndex <= newStartIndex) {
    
    
    // 旧列表中遇到Undefine节点则跳过对比
    if (oldStartNode === undefined) {
    
    
      oldStartNode = prevChildren[++oldStartIndex]
    } else if (oldEndNode === undefined) {
    
    
      oldEndNode = prevChildren[--oldStartIndex]
    } else if (oldStartNode.key === newStartNode.key) {
    
    
      // 当两个头节点相同时,节点不需要处理,两个指针的坐标向后移动一位
      patch(oldStartNode, newStartNode, parent)

      oldStartIndex++
      newStartIndex++
      oldStartNode = prevChildren[oldStartIndex]
      newStartNode = nextChildren[newStartIndex]
    } else if (oldEndNode.key === newEndNode.key) {
    
    
      // 当两个尾节点相同时,节点不需要处理,两个指针的坐标向前移动一位
      patch(oldEndNode, newEndNode, parent)

      oldEndIndex--
      newEndIndex--
      oldEndNode = prevChildren[oldEndIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldStartNode.key === newEndNode.key) {
    
    
      // 当旧头节点跟新尾节点相同时,需要移动旧头节点,旧指针向后移动一位,新指针向前移动一位
      patch(oldStartNode, newEndNode, parent)
      parent.insertBefore(oldStartNode.el, oldEndNode.el.nextSibling)
      oldStartIndex++
      newEndIndex--
      oldStartNode = prevChildren[oldStartIndex]
      newEndNode = nextChildren[newEndIndex]
    } else if (oldEndNode.key === newStartNode.key) {
    
    
      // 当旧尾节点和新头节点相同时,需要移动旧头节点,旧指针向前移动一位,新指针向后移动一位
      patch(oldEndNode, newStartNode, parent)
      parent.insertBefore(oldEndNode.el, oldStartNode.el)
      oldEndIndex--
      newStartIndex++
      oldEndNode = prevChildren[oldEndIndex]
      newStartNode = nextChildren[newStartIndex]
    } else {
    
    
      // 四个节点对比的过程中没有发现相同的节点时
      // 先看看新列表的头节点是否存在于旧列表中
      let newKey = newStartNode.key,
        oldIndex = prevChildren.findIndex(child => child && (child.key === newKey));
      // 如果不存在,直接创建放在最前方即可
      if (oldIndex === -1) {
    
    
        mount(newStartNode, parent, oldStartNode.el)
      } else {
    
    
      // 如果存在,将旧节点移动到第一个节点,并在旧列表中置成undefine, 跳过对比过程
        let prevNode = prevChildren[oldIndex]
        patch(prevNode, newStartNode, parent)
        parent.insertBefore(prevNode.el, oldStartNode.el)
        prevChildren[oldIndex] = undefined
      }
      // 更新新列表的对比节点
      newStartIndex++
      newStartNode = nextChildren[newStartIndex]
    }
  }
  // 当新列表的newStartIndex 大于newEndIndex,说明新列表删除的节点
  if (newStartIndex > newEndIndex) {
    
    
    while (oldStartIndex <= oldStartIndex) {
    
    
      if (!prevChildren[oldStartIndex]) {
    
    
        oldStartIndex++
        continue
      }
      parent.removeChild(prevChildren[oldStartIndex++].el)
    }
  } else if (oldStartIndex> oldEndIndex ) {
    
    
    // 当旧列表的oldEndIndex 小于oldStartIndex,说明新列表新增了节点
    for (let i = newStartIndex; i <= newEndIndex; i++) {
    
    
      mount(nextChildren[i], parent, prevStartNode.el)
    }
  }
}

summary

Re-learned Vue2's diff algorithm: double-ended comparison method, with new gains, the thinking is much clearer. I will write a Vue3 diff algorithm later~

reference link

Guess you like

Origin blog.csdn.net/qq_34086980/article/details/131552585