SoFunction
Updated on 2025-04-06

vue3 diff algorithm example

1. Possibility (common):

1.

Old: a b c
New: a b c d

2.

Old: a b c
New: d a b c

3.

Old: a b c d
New: a b c

4.

Old: d a b c
New: a b c

5.

Old: a b c d e i f g
New: ab e c d h g

The corresponding real virtual node (for easy understanding, the text uses letters instead):

// vnode objectconst a = {
  type: 'div', // Label  props: {style: {color: 'red'}}, // Attributes  children: [], // Sub-elements  key: 'key1', // key
  el: '<div style="color: 'red'"></div>', // Real dom node  ...
}

2. Find rules

Remove the same parts in front and back

// c1 represents the old child node, c2 represents the new child nodeconst patchKeyedChildren = (c1, c2) => {
  let i = 0
  let e1 =  - 1
  let e2 =  - 1
  // Compare from the previous one  while (i <= e1 && i <= e2) {
    const n1 = c1[i]
    const n2 = c2[i]
    // Is the tag and key the same    // if ( ===  &&  === )
    if (n1 === n2) {
      // Continue to compare its properties and children    } else {
      break
    }
    i++
  }
  // From the back  while (i <= e1 && i <= e2) {
    const n1 = c1[e1]
    const n2 = c2[e2]
    // Is the tag and key the same    // if ( ===  &&  === )
    if (n1 === n2) {
      // Continue to compare its properties and children    } else {
      break
    }
    e1--
    e2--
  }
  (i, e1, e2)
}
// Call examplepatchKeyedChildren(['a', 'b', 'c', 'd'], ['a', 'b', 'c'])

Through this function you can get:

1.

Old: a b c
New: a b c d

i = 3  e1 = 2  e2 = 3

2.

Old: a b c
New: d a b c

i = 0  e1 = -1  e2 = 0

3.

Old: a b c d
New: a b c

i = 3  e1 = 3  e2 = 2

4.

Old: d a b c
New: a b c

i = 0  e1 = 0  e2 = -1

5.

Old: a b c d e i f g
New: ab e c d h g

i = 2  e1 = 5  e2 = 5

Extensions:

Old: a b c
New: a b c d e f
i = 3  e1 = 2  e2 = 5

Old: a b c
New: a b c
i = 3  e1 = 2  e2 = 2

Old: e d a b c
New:    a b c
i = 0  e1 = 1  e2 = -1

Old: c d e
New: e c d h
i = 0  e1 = 2  e2 = 3

From the above results we can find the rules:

  • When i is greater than e1, just add new child nodes
  • When i is greater than e2, just delete the old child node
// When i is greater than e1if (i > e1) {
  if (i <= e2) {
    while (i <= e2) {
      const nextPos = e2 + 1
      const anchor = nextPos <  ? c2[nextPos].el : null
      // Add new child node c2[i] in front of anchor      // todo
      i++
    }
  }
}
// When i is greater than e2else if (i > e2) {
  if (i <= e1) {
    while (i <= e1) {
      // Delete the old child node c1[i]      // todo
      i++
    }
  }
}
  • Other, special treatment
// Otherslet s1 = i
let s2 = i
// Use new child nodes as referenceconst keyToNewIndexMap = new Map()
for (let i = s2; i <= e2; i++) {
  // The key of the node is used as the unique value  // (c2[i].key, i)
  (c2[i], i)
}
// The total number of newconst toBePatched = e2 - s2 + 1
// Record the index of the new child node in the old child nodeconst newIndexToOldIndexMap = new Array(toBePatched).fill(0)
// Loop old child nodesfor (let i = s1; i <= e1; i++) {
  const oldChild = c1[i]
  // let newIndex = ()
  let newIndex = (oldChild)
  // does not exist in the new child node  if (newIndex === undefined) {
    // Delete oldChild    // todo
  } else {
    newIndexToOldIndexMap[newIndex - s2] = i + 1 // It will never be equal to 0, so 0 can mean that it needs to be created    // Continue to compare its properties and children    // todo
  }
}
(newIndexToOldIndexMap)
// Need to move the positionfor (let i = toBePatched - 1; i >= 0; i--) {
  let index = i + s2
  let current = c2[index]
  let anchor = index + 1 <  ? c2[index + 1].el : null
  if (newIndexToOldIndexMap[i] === 0) {
    // Insert new node in front of anchor current    // todo
  } else {
    // Insert the corresponding old node.el in front of the anchor, the element is equal to the corresponding old node.el (assigned in other codes)    // todo
  }
}

The first and second types are relatively simple, so don’t explain too much. Let’s take a look at the third type, as follows as an example

Serial number: 0 1  2 3 4 5  6 7
Old: (a b) c d e i (f g)
New: (a b) e c d h (f g)

  • The front a b and the back f g are the same, and will be removed, leaving only the middle out of order
  • Taking the new node as a reference, looping through the old node and removing the new nodes that do not exist from the old node, such as i
  • Mark the node that does not exist in the old one. If it does not exist, it means that it needs to be created; if it has a serial number +1, it means that it can be reused.

Tags:       4+1 2+1 3+1  0
New: (...)   e   c   d   h (...)

  • From the back to the front, h is 0, create, put it in front of its next f; d is not 0, reuse d in the old, put it in front of h; c is not 0, reuse c in the old, put it in front of d; e is not 0, reuse e in the old, put it in front of c; e is not 0, reuse e in the old, put it in front of c

Up to the purpose, the replacement of new and old elements has been completed, but have you found a problem? Ec d h has moved all four elements once. We can see that if only e is moved and h is created, c and d remain unchanged, the efficiency will be higher.

3. Algorithm optimization

1.

Serial number: 0 1  2 3 4 5  6 7
Old: (a b) c d e i (f g)
New: (a b) e c d h (f g)
The corresponding tag is [5, 3, 4, 0]

2.

Serial number: 0 1 2 3 4 5
Old: cd e i f
New: e c d f g j
The corresponding tag is [3, 1, 2, 5, 6, 0]

From the above two examples, we can see:
The first optimal solution is to find c d, just move e, create h
The second optimal solution is to find c d f g, just move e and create j

process:

  • Find the longest incremental subsequence from the tag
  • Find the corresponding index value through the longest incremental sub-sequence
  • Find the corresponding value by index value

Note that 0 means creation directly and does not participate in calculations.

example:

  • The longest incrementer sequence of [3, 1, 2, 5, 6, 0] is [1, 2, 5, 6],
  • The corresponding index is [1, 2, 3, 4],
  • Then we traverse e c d f g j, the marker is 0, such as j, and create it directly; c d f g indexes are equal to 1 2 3 4, respectively, and remain unchanged; e equals 0, and move
// Need to move the position// Find the index value corresponding to the longest incremental sub-sequence, such as: [5, 3, 4, 0] -> [1, 2]let increment = getSequence(newIndexToOldIndexMap)
(increment)
let j =  - 1
for (let i = toBePatched - 1; i >= 0; i--) {
  let index = i + s2
  let current = c2[index]
  let anchor = index + 1 <  ? c2[index + 1].el : null
  if (newIndexToOldIndexMap[i] === 0) {
    // Insert new node in front of anchor current    // todo
  } else {
    if (i !== increment[j]) {
      // Insert the corresponding old node.el in front of the anchor, the element is equal to the corresponding old node.el (assigned in other codes)      // todo
    } else { // Unchanged      j--
    }
  }
}

The longest incremental subsequence

// The longest incremental subsequence, /wiki/Longest_increasing_subsequencefunction getSequence(arr) {
  const len = 
  const result = [0] // Use the first item as the basis  const p = () // Mark the index, slice to copy a new array as shallow  let resultLastIndex
  let start
  let end
  let middle
  for (let i = 0; i < len; i++) {
    let arrI = arr[i]
    if (arrI !== 0) { // vue equals 0, which means that it needs to be created      resultLastIndex = result[ - 1]
      // Insert to the end      if (arr[resultLastIndex] < arrI) {
        (i)
        p[i] = resultLastIndex // Who is the one in front        continue
      }
      // Incremental sequence, binary search      start = 0
      end =  - 1
      while(start < end) {
        middle = (start + end) >> 1 // Equivalent to ((start + end)/2)        if (arr[result[middle]] < arrI) {
          start = middle + 1
        } else  {
          end = middle
        }
      }
      // Find the nearest one that is bigger than it, replace it      if (arr[result[end]] > arrI) {
        result[end] = i
        if (end > 0) {
          p[i] = result[end - 1] // Who is the one in front        }
      }
    }
  }
  let i = 
  let last = result[i - 1]
  while(i-- > 0) {
    result[i] = last
    last = p[last]
  }
  return result
}
(getSequence([5, 3, 4, 0])) // [1, 2]
(getSequence([3, 1, 2, 5, 6, 0])) // [ 1, 2, 3, 4 ]

Supplements for explanation...

Complete code

// The longest incremental subsequence, /wiki/Longest_increasing_subsequencefunction getSequence(arr) {
  const len = 
  const result = [0] // Use the first item as the basis  const p = () // Mark the index, slice to copy a new array as shallow  let resultLastIndex
  let start
  let end
  let middle
  for (let i = 0; i < len; i++) {
    let arrI = arr[i]
    if (arrI !== 0) { // vue equals 0, which means that it needs to be created      resultLastIndex = result[ - 1]
      // Insert to the end      if (arr[resultLastIndex] < arrI) {
        (i)
        p[i] = resultLastIndex // Who is the one in front        continue
      }
      // Incremental sequence, binary search      start = 0
      end =  - 1
      while(start < end) {
        middle = (start + end) >> 1 // Equivalent to ((start + end)/2)        if (arr[result[middle]] < arrI) {
          start = middle + 1
        } else  {
          end = middle
        }
      }
      // Find the nearest one that is bigger than it, replace it      if (arr[result[end]] > arrI) {
        result[end] = i
        if (end > 0) {
          p[i] = result[end - 1] // Who is the one in front        }
      }
    }
  }
  let i = 
  let last = result[i - 1]
  while(i-- > 0) {
    result[i] = last
    last = p[last]
  }
  return result
}
// c1 represents the old child node, c2 represents the new child nodeconst patchKeyedChildren = (c1, c2) => {
  let i = 0
  let e1 =  - 1
  let e2 =  - 1
  // Compare from the previous one  while (i <= e1 && i <= e2) {
    const n1 = c1[i]
    const n2 = c2[i]
    // Is the tag and key the same    // if ( ===  &&  === )
    if (n1 === n2) {
      // Continue to compare its properties and children    } else {
      break
    }
    i++
  }
  // From the back  while (i <= e1 && i <= e2) {
    const n1 = c1[e1]
    const n2 = c2[e2]
    // Is the tag and key the same    // if ( ===  &&  === )
    if (n1 === n2) {
      // Continue to compare its properties and children    } else {
      break
    }
    e1--
    e2--
  }
  (i, e1, e2)
  // When i is greater than e1  if (i > e1) {
    if (i <= e2) {
      while (i <= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos <  ? c2[nextPos].el : null
        // Add child node c2[i] in front of anchor        // todo
        i++
      }
    }
  }
  // When i is greater than e2  else if (i > e2) {
    if (i <= e1) {
      while (i <= e1) {
        // Delete child node c1[i]        // todo
        i++
      }
    }
  }
  // Others  else {
    let s1 = i
    let s2 = i
    // Use new child nodes as reference    const keyToNewIndexMap = new Map()
    for (let i = s2; i <= e2; i++) {
      // The key of the node is used as the unique value      // (c2[i].key, i)
      (c2[i], i)
    }
    // The total number of new    const toBePatched = e2 - s2 + 1
    // Record the index of the new child node in the old child node    const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
    // Loop old child nodes    for (let i = s1; i <= e1; i++) {
      const oldChild = c1[i]
      // let newIndex = ()
      let newIndex = (oldChild)
      // does not exist in the new child node      if (newIndex === undefined) {
        // Delete oldChild        // todo
      } else {
        newIndexToOldIndexMap[newIndex - s2] = i + 1 // It will never be equal to 0, so 0 can mean that it needs to be created        // Continue to compare its properties and children        // todo
      }
    }
    (newIndexToOldIndexMap)
    // Need to move the position    // Find the index value corresponding to the longest incremental sub-sequence, such as: [5, 3, 4, 0] -> [1, 2]    let increment = getSequence(newIndexToOldIndexMap)
    (increment)
    let j =  - 1
    for (let i = toBePatched - 1; i >= 0; i--) {
      let index = i + s2
      let current = c2[index]
      let anchor = index + 1 <  ? c2[index + 1].el : null
      if (newIndexToOldIndexMap[i] === 0) {
        // Insert new node in front of anchor current        // todo
      } else {
        if (i !== increment[j]) {
          // Insert the corresponding old node.el in front of the anchor, the element is equal to the corresponding old node.el (assigned in other codes)          // todo
        } else { // Unchanged          j--
        }
      }
    }
  }
}
// Call examplepatchKeyedChildren(['c', 'd', 'e', 'i', 'f', 'g'], ['e', 'c', 'd', 'f', 'g', 'j'])

The above is the detailed content of the vue3 diff algorithm example. For more information about the vue3 diff algorithm, please follow my other related articles!