What is the virtual DOM?

  • Virtual DOM(Virtual DOM) is a common JS object to describe the DOM object, because it is not a real DOM object, so it is called Virtual DOM
/ / true DOM
<ul id='ulist'>
  <li id='first'>This is the first List element</li>
  <li id='second'>This is the second List element</li>
</ul>
/ / virtual DOM
var element = {
    element: 'ul'.props: {
        id:"ulist"
    },
    children: [{element: 'li'.props: { id:"first" }, children: ['This is the first List element'] {},element: 'li'.props: { id:"second" }, children: ['This is the second List element']]}}Copy the code

Why use the virtual DOM

  • Manual DOM manipulation is cumbersome, and browser compatibility issues need to be considered. Although there are libraries such as jQuery to simplify DOM manipulation, the complexity of DOM manipulation will increase with the complexity of the project

  • In order to simplify the complex manipulation of DOM, various MVVM frameworks have emerged to solve the problem of view and state synchronization

  • To simplify view manipulation we could use a template engine, but the template engine didn’t solve the problem of tracking state changes, so the virtual DOM emerged

  • The advantage of the virtual DOM is that you don’t need to update the DOM immediately when the state changes. You just need to create a virtual tree to describe the DOM. The virtual DOM inside will figure out how to effectively (diff) update the DOM

  • Virtual DOM can maintain the state of the program and track the last state, which can better maintain the relationship between view and state and improve rendering performance in the case of complex views

  • In addition to rendering DOM, virtual DOM can also realize SSR(nuxt.js/Next-js), Native application (Weex/React Native), small program (MPvue /uni-app), etc

3. Virtual DOM library Snabbdom

  • The virtual DOM used internally in Vue 2.x is a modified Snabbdom
  • Approximately 200 SLOC (single line of code)
  • Extensible through modules
  • Source code is developed using TypeScript
  • One of the fastest virtual DOM

4. Basic use of Snabbdom

  1. Initialize the project and install the package tool Parcel
CD snabbdom-demo # create package.json NPM init -yCopy the code
  1. Configure scripts, configure start project commands, and package project commands
// --open means automatically opening the browser
"scripts": {
  "dev": "parcel index.html --open"."build": "parcel build index.html"
}
Copy the code
  1. Create a project directory structure
│ ├.html │ package.json ├ ─ SRC01-basicusage.js
Copy the code
  1. Install Snabbdom
npm install snabbdom@2.1. 0
Copy the code
  1. Import the two core functions of Snabbdom
  • Init () is a higher-order function that returns patch()
  • H () Returns the VNode
  • The exports field in package.json is used mainly to map paths and set child paths
  • Because parcel and Webpakc5 did not previously support the exports field in package.json, the path is completed here
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])
Copy the code
  1. Snabbdom case a
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])

The h() function is mainly used to generate vNodes
// First argument: label + selector
// The second argument: if it is a string, it is the text content of the tag
let vnode = h('div#container.cls'.'Hello World')
let app = document.querySelector('#app')

// First argument: old VNode, (can also be a real DOM element, patch will convert it to VNode)
// Second argument: new VNode
// Return a new VNode
let oldVnode = patch(app, vnode)

vnode = h('div#container.xxx'.'Hello Snabbdom')
// Patch compares the old and new vNodes and updates the differences to real DOM elements
patch(oldVnode, vnode)
Copy the code
  1. Snabbdom second case
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])

// The second argument: if it is an array, it is a child of the tag
let vnode = h('div#container', [
  h('h1'.'Hello Snabbdom'),
  h('p'.'This is a p'.)])let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)

setTimeout(() = > {
  // Change the contents of the div after two seconds
  vnode = h('div#container', [
    h('h1'.'Hello World'),
    h('p'.'Hello P')
  ])
  patch(oldVnode, vnode)

  Delete the contents of the div after two seconds
  patch(oldVnode, h('! '))},2000);
Copy the code

5. Snabbdom module

  • Snabbdom’s core library does not handle DOM element attributes, styles, and events…… But we can do this by registering modules that Snabbdom provides by default

  • Modules in the Snabbdom are used to extend the functionality of the Snabbdom

  • Modules in Snabbdom are implemented by registering global dog functions

  • Snabbdom:

    1. attributes

      • Set the attributes of the DOM element usingsetAttribute(a)
      • Handles properties of Boolean type
    2. props

      • andattributesThe module is similar to setting the attributes of the DOM elementelement[attr] = value
      • Boolean type attributes are not handled
    3. class

      • Switching class styles
      • Note: Setting class styles for elements is done byselThe selector
    4. dataset

      • Set up thedata-*Custom property of
    5. eventlisteners

      • Register and remove events
    6. style

      • Set inline styles to support animation
      • delayed/remove/destroy
  • Snabbdom module usage steps:

    1. Import the required modules
    2. Register modules in init()
    3. When creating a VNode using the h() function, you can set the second parameter to the object and move the other parameters back
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

// 1. Import modules
import { styleModule } from 'snabbdom/build/package/modules/style'
import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners'

// 2. Register module
const patch = init([
  styleModule,
  eventListenersModule
])

// 3. Pass the second argument to h() to the data (object) used in the module.
let vnode = h('div', [
  h('h1', { style: { backgroundColor: 'red'}},'Hello World'),
  h('p', { on: { click: eventHandler } }, 'Hello P')])function eventHandler() {
  console.log('Don't point at me. It hurts.')}let app = document.querySelector('#app')
patch(app, vnode)
Copy the code

Six, Snabbdom source code analysis

The main analysis of Snabbdom core source code

  1. Use the h() function to create JavaScript objects (vNodes) that describe the real DOM
  2. Init () sets the module, creates the patch()
  3. Patch () compares the old and new vNodes
  4. Update the changed content to the real DOM tree

6-1 h()

  • The h() function is used to create vNode objects, similar to the h() function in Vue

  • The h() function in Snabbdom is written in TS, using the concept of function overloading

  • Function overloading

    1. A function with a different number of arguments or type
    2. There is no concept of overloading in JavaScript
    3. Overloading exists in TypeScript, but overloading is implemented by adjusting parameters in code

The implication of overloading is that when add passes two arguments, the first method is called, and when add passes three arguments, the second method is called. This is function overloading

function add (a: number, b: number) {
  console.log(a + b)
}
function add (a: number, b: number, c: number) {
  console.log(a + b + c)
}
add(1.2)
add(1.2.3)
Copy the code

H () function source

// overloading the h function
export function h (sel: string) :VNode
export function h (sel: string, data: VNodeData | null) :VNode
export function h (sel: string, children: VNodeChildren) :VNode
export function h (sel: string, data: VNodeData | null, children: VNodeChildren) :VNode
export function h (sel: any, b? : any, c? : any) :VNode {
  var data: VNodeData = {}
  var children: any
  var text: any
  var i: number// Handle arguments to implement overloading mechanismsif (c ! = =undefined) {// handle three arguments //sel,data,children/text
    if (b ! = =null) {
      data = b
    }
    if (is.array(c)) {
     // if c is array
      children = c
    } else if (is.primitive(c)) {
      // If c is a string or number
      text = c
    } else if (c && c.sel) {
      // If c is VNode
      children = [c]
    }
  } else if(b ! = =undefined&& b ! = =null) {
    if (is.array(b)) {
      children = b
    } else if (is.primitive(b)) {
      // If b is a string or number
      text = b
    } else if (b && b.sel) {
      // If b is VNode
      children = [b]
    } else { data = b }
  }
  if(children ! = =undefined) {
    // Handle primitive values in children (string/number)
    for (i = 0; i < children.length; ++i) {
      // If child is string/number, create a text node
      if (is.primitive(children[i])) children[i] = vnode(undefined.undefined.undefined, children[i], undefined)}}if (
    sel[0= = ='s' && sel[1= = ='v' && sel[2= = ='g' &&
    (sel.length === 3 || sel[3= = ='. ' || sel[3= = =The '#')) {// If SVG is used, add namespace
    addNS(data, children, sel)
  }
  / / return VNode
  return vnode(sel, data, children, text, undefined)};Copy the code

6-2 VNode

A VNode is a virtual node, a JavaScript object that describes a DOM element

export interface VNode {
  / / selector
  sel: string | undefined;
  // Node data: attributes/styles/events etc
  data: VNodeData | undefined;
  // Child nodes, and text are mutually exclusive
  children: Array<VNode | string> | undefined;
  // Record the actual DOM corresponding to the vNode
  elm: Node | undefined;
  // The contents of this node are mutually exclusive with children
  text: string | undefined;
  / / optimization
  key: Key | undefined;
}

export function vnode (sel: string | undefined,
                      data: any | undefined,
                      children: Array<VNode | string> | undefined,
                      text: string | undefined,
                      elm: Element | Text | undefined) :VNode {
  const key = data === undefined ? undefined : data.key
  return { sel, data, children, text, elm, key }
}
Copy the code

6-3 Patch overall process analysis

  • patch(oldVnode, newVnode)

  • Patch, also known as patching, renders the changed content in the new node to the real DOM, and finally returns the new node as the old node for the next processing

  • Check whether the old and new VNodes have the same key and SEL.

  • If it is not the same node, delete the previous content and re-render

  • If the node is the same, check whether the new VNode has a text. If the text is different from that of the oldVnode, update the text directly

  • If the new VNode has children, the diff algorithm is used to determine whether the child node has changed

  • The diff procedure only makes same-level comparisons

6-4 init()

  • Init (modules, domApi) returns patch(), which is a higher-order function

  • Why use higher-order functions?

    1. Because patch() is called externally multiple times, each call depends on arguments such as modules/domApi/ CBS
    2. Init () can be closed internally by higher-order functions, and the returned patch() can access modules/domApi/ CBS without needing to be recreated
  • Init () first collects all of the module’s hook functions and stores them in a CBS object before returning patch()

Init () function source

const hooks: Array<keyof Module> = ['create'.'update'.'remove'.'destroy'.'pre'.'post']
export function init (modules: Array<Partial<Module>>, domApi? : DOMAPI) {
  let i: number
  let j: number
  const cbs: ModuleHooks = {
    create: [].update: [].remove: [].destroy: [].pre: [].post: []}// Initialize the API
  constapi: DOMAPI = domApi ! = =undefined ? domApi : htmlDomApi
  // Store all incoming module hook methods in a CBS object
  CBS = [create: [fn1, fn2], update: [],...
  for (i = 0; i < hooks.length; ++i) {
    // cbs['create'] = []
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      // const hook = modules[0]['create']
      const hook = modules[j][hooks[i]]
      if(hook ! = =undefined) {
        (cbs[hooks[i]] asAny []).push(hook)}}}......return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {... }}Copy the code

6-5 patch()

  • The patch() function will pass in the old and new VNodes, compare the differences, render the differences into the DOM, and return the new VNode as the oldVnode for the next patch()

  • Patch () execution process

    1. First execute the hook function in the module

    2. If oldVnode and vnode are the same (key and SEL are the same), call patchVnode() to find the difference and update the DOM.

    3. If oldVnode is a DOM element (rendering for the first time), convert the DOM element to oldVnode. Then call createElm() to convert the vNode to the real DOM. Insert the newly created DOM element into the parent, remove the old node, and trigger the user-set CREATE hook function

Patch () the source code

return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {
  let i: number, elm: Node, parent: Node
  // Save the queue of newly inserted nodes in order to trigger the hook function
  const insertedVnodeQueue: VNodeQueue = []
  // Execute the module's Pre hook function
  for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
      // If oldVnode is not VNode, create VNode and set elm
      if(! isVnode(oldVnode)) {// Convert the DOM element to an empty VNode
        oldVnode = emptyNodeAt(oldVnode)
      }
      // If the new and old nodes are the same node (key and SEL are the same)
      if (sameVnode(oldVnode, vnode)) {
        // Find the node differences and update the DOM
        patchVnode(oldVnode, vnode, insertedVnodeQueue)
      } else {
        // If the old and new nodes are different, vNode creates the corresponding DOM
        // Get the current DOM element
        elm = oldVnode.elm!
        parent = api.parentNode(elm) as Node
        // Trigger init/create hook function to create DOM
        createElm(vnode, insertedVnodeQueue)

        if(parent ! = =null) {
          // If the parent node is not empty, insert the DOM corresponding to the vnode into the documentapi.insertBefore(parent, vnode.elm! , api.nextSibling(elm))// Remove the old node
          removeVnodes(parent, [oldVnode], 0.0)}}// Execute the user-set insert hook function
  for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data! .hook! .insert! (insertedVnodeQueue[i]) }// Execute the module's POST hook function
  for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
  return vnode
}
Copy the code

6-6 createElm()

  • CreateElm () converts the VNode to a real DOM element, stores the DOM element in the ELM attribute of the vNode object, and returns the created DOM. The created DOM element is not mounted to the DOM tree

  • CreateElm ();

    1. The user-set init hook function is triggered first
    2. If the selector is! To create a comment node
    3. If the selector is empty, a text node is created
    4. If the selector is not empty, parse the selector, set the tag’s ID and class properties, and execute the module’s CREATE hook function
    5. If the vnode has children, create a DOM corresponding to the child vNode and append it to the DOM tree
    6. If the vNode text value is string/number, create a text node and chase it into the DOM tree
    7. Execute the user-set CREATE hook function
    8. If there is a user-set insert hook function, add vNode to the queue

CreateElm () the source code

function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue) :Node {
    let i: any
    let data = vnode.data
    
    if(data ! = =undefined) {
      // Execute the user-set init hook function
      constinit = data.hook? .initif (isDef(init)) {
        init(vnode)
        data = vnode.data
      }
    }
    const children = vnode.children
    const sel = vnode.sel
    if (sel === '! ') {
      // If the selector is! To create a comment node
      if (isUndef(vnode.text)) {
        vnode.text = ' '
      }
      vnode.elm = api.createComment(vnode.text!)
    } else if(sel ! = =undefined) {
      // If the selector is not empty
      // Parse the selector
      // Parse selector
      const hashIdx = sel.indexOf(The '#')
      const dotIdx = sel.indexOf('. ', hashIdx)
      const hash = hashIdx > 0 ? hashIdx : sel.length
      const dot = dotIdx > 0 ? dotIdx : sel.length
      consttag = hashIdx ! = = -1|| dotIdx ! = = -1 ? sel.slice(0.Math.min(hash, dot)) : sel
      const elm = vnode.elm = isDef(data) && isDef(i = data.ns)
        ? api.createElementNS(i, tag)
        : api.createElement(tag)
      if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot))
      if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/./g.' '))
      // Execute the module's CREATE hook function
      for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
      // If there are child nodes in the vNode, create the DOM element corresponding to the child vnode and append it to the DOM tree
      if (is.array(children)) {
        for (i = 0; i < children.length; ++i) {
          const ch = children[i]
          if(ch ! =null) {
            api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue))
          }
        }
      } else if (is.primitive(vnode.text)) {
        // If the vNode text value is string/number, create a text node and append it to the DOM tree
        api.appendChild(elm, api.createTextNode(vnode.text))
      }
      consthook = vnode.data! .hookif (isDef(hook)) {
        // Execute the user passed hook createhook.create? .(emptyNode, vnode)if (hook.insert) {
          // Add the vNode to the queue in preparation for the insert hook
          insertedVnodeQueue.push(vnode)
        }
      }
    } else {
      // If the selector is empty, create a text node
      vnode.elm = api.createTextNode(vnode.text!)
    }
    // Returns the newly created DOM
    return vnode.elm
  }
Copy the code

6-7 patchVnode()

  • PatchVnode () is a method called in Patch (), which mainly compares the differences between oldVnode and Vnode and renders the differences into DOM

  • PatchVnode () execution process

    1. The user-set prePatch hook function is first executed

    2. To execute the CREATE hook function, first execute the module’s CREATE ** hook function, and then execute the user-set CREATE hook function

    3. If vnode.text is not defined, check whether both oldvNode. children and vnode.children have values, and then call updateChildren(), using the diff algorithm to compare and update the children

    4. If vnode.children has a value and oldvNode.children has no value, then empty the DOM element and call addVnodes() to add children in batches

    5. If oldvNode.children has a value but vnode.children does not, call removeVnodes() to remove the children in batches

    6. If oldvNode. text has a value, empty the content of the DOM element

    7. If vnode.text is set and differs from oldvNode. text, if the old node has children, remove all of them and set the DOM element’s textContent to vnode.text

    8. Finally, execute the user-set PostPatch hook function

PatchVnode () the source code

function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
   consthook = vnode.data? .hook// Execute the user-set prepatch hook function firsthook? .prepatch? .(oldVnode, vnode)const elm = vnode.elm = oldVnode.elm!
   const oldCh = oldVnode.children as VNode[]
   const ch = vnode.children as VNode[]
 	// Return if the old and new vNodes are the same
   if (oldVnode === vnode) return
   if(vnode.data ! = =undefined) {
     // Execute the module's update hook function
     for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
     // Execute the user-set update hook functionvnode.data.hook? .update? .(oldVnode, vnode) }// If vnode.text is not defined
   if (isUndef(vnode.text)) {
     // If both new and old nodes have children
     if (isDef(oldCh) && isDef(ch)) {
       // Call updateChildren to compare and update child nodes
       if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) }else if (isDef(ch)) {
       // If the new node has children, the old node has no children
     	// Empty the dom element if the old node has text
       if (isDef(oldVnode.text)) api.setTextContent(elm, ' ')
       // Add child nodes in batches
       addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
     } else if (isDef(oldCh)) {
       // If the old node has children, the new node has no children
     	// Remove child nodes in batches
       removeVnodes(elm, oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
       // If the old node has text, clear the DOM element
       api.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// If vnode.text is not set
     if (isDef(oldCh)) {
       // If the old node has children, remove it
       removeVnodes(elm, oldCh, 0, oldCh.length - 1)}// Set the DOM element's textContent to vnode.text
     api.setTextContent(elm, vnode.text!)
   }
   // Finally execute the user-set postpatch hook functionhook? .postpatch? .(oldVnode, vnode) }Copy the code

6-8 updateChildren()

  • UpdateChildren () is the core of the diff algorithm, which is mainly used to compare the children of the old and new nodes and update the DOM

  • UpdateChildren ()

You want to compare the difference of two trees, if you take the first tree of each node and the second class in turn each node of the tree, so that the time complexity of O (n ^ 3), and at the time of DOM manipulation is few will move a parent/update to a child node, so you just need to find other child nodes in comparison at the same level, And then compare it at the next level, so the time of the algorithm is O(n).

When comparing nodes of the same level, the start and end nodes of the new and old node arrays will be marked with index, and the index will be moved during traversal

When comparing the start and end nodes, there are four cases

- oldStartVnode/newStartVnode (old start node/new start node) - oldEndVnode/newEndVnode (old end node/new end node) - oldStartVnode / OldEndVnode (old start node/new end node) - oldEndVnode/newStartVnode (old end node/new start node)Copy the code

Case 1, 2: oldStartVnode/newStartVnode (old start node/new start node) oldEndVnode/newEndVnode (old end node/new end node)

If oldStartVnode and newStartVnode are samevNodes (with the same key and SEL), patchVnode() is called to compare and update the nodes. Move the old start and new start indexes back oldStartIdx++ / oldEndIdx++

Case 3: oldStartVnode/newEndVnode (old start node/new end node) is the same

Call patchVnode() to compare and update nodes, and move the DOM element corresponding to oldStartVnode to the right – update index

Case 4: oldEndVnode/newStartVnode (old end node/new start node) is the same

PatchVnode () is called to compare and update nodes, and the DOM element corresponding to oldEndVnode is moved to the left to update the index

  • If not, iterate over the new node and use the key of newStartNode to find the same node in the array of old nodes. If not, newStartNode is a new node. Create a DOM element corresponding to the new node and insert it into the DOM tree. Determine whether the SEL selector of the new node is the same as that of the old node. If not, it indicates that the node has been modified. Re-create the corresponding DOM element and insert it into the DOM tree

  • At the end of the loop, if the array of the old node has been traversed first (oldStartIdx > oldEndIdx), the new node has a surplus, and the remaining nodes are batch inserted to the right

  • At the end of the loop, if the array of the new node has been traversed first (newStartIdx > newEndIdx), the old node has surplus, and the remaining nodes are deleted in batches

UpdateChildren () the source code

function updateChildren (parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) {
  let oldStartIdx = 0
  let newStartIdx = 0
  let oldEndIdx = oldCh.length - 1
  let oldStartVnode = oldCh[0]
  let oldEndVnode = oldCh[oldEndIdx]
  let newEndIdx = newCh.length - 1
  let newStartVnode = newCh[0]
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx: KeyToIndexMap | undefined
  let idxInOld: number
  let elmToMove: VNode
  let before: any

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // After index changes, the node may be set to null
    if (oldStartVnode == null) {
      // The node is an empty move index
      oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left
    } else if (oldEndVnode == null) {
      oldEndVnode = oldCh[--oldEndIdx]
    } else if (newStartVnode == null) {
      newStartVnode = newCh[++newStartIdx]
    } else if (newEndVnode == null) {
      newEndVnode = newCh[--newEndIdx]
    // Compare the four cases of start and end nodes
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      // 1. Compare the old start node with the new start node
      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      // 2. Compare the old end node with the new end node
      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      // 3. Compare the old start node with the new end nodepatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldStartVnode.elm! , api.nextSibling(oldEndVnode.elm!) ) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      // 4. Compare the old end node with the new start nodepatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldEndVnode.elm! , oldStartVnode.elm!) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else {
      // The start node and the end node are different
      // Use newStartNode's key to find the same node in the old node array
      // Set the key and index objects
      if (oldKeyToIdx === undefined) {
        oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
      }
      // Iterate over newStartVnode to find the oldVnode index with the same key from the old node
      idxInOld = oldKeyToIdx[newStartVnode.key as string]
      // If it is a new VNode
      if (isUndef(idxInOld)) { // New element
        // If not found, newStartNode is the new node
        // Create an element to insert into the DOM tree
        api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
      } else {
        // If an old node with the same key is found, record it in elmToMove traversal
        elmToMove = oldCh[idxInOld]
        if(elmToMove.sel ! == newStartVnode.sel) {// If the old and new nodes have different selectors
          // Create a DOM element corresponding to the new start node and insert it into the DOM tree
          api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
        } else {
          // If same, patchVnode()
          // Move the elmToMove element to the left
          patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
          oldCh[idxInOld] = undefined as any
          api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!)
        }
      }
      // Re-assign newStartVnode to the next new node
      newStartVnode = newCh[++newStartIdx]
    }
  }
  // When the loop ends, the old node array completes first or the new node array completes first
  if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
    if (oldStartIdx > oldEndIdx) {
      // If the old node array is traversed first, new nodes are left
      // Insert the remaining new nodes to the right
      before = newCh[newEndIdx + 1] = =null ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else {
      // If the new node array is traversed first, the old node is left
      // Delete old nodes in batches
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }
}
Copy the code