Vue source code learning-virtual Dom and diff algorithm

foreword

This is a series of articles on learning source code. If you are interested, you can continue to read other articles
Vue source code learning - what does new Vue initialization do?
Vue source code learning - data responsive principle
Vue source code learning - asynchronous update queue and nextTick principle

Because the Diff algorithm calculates the difference of the virtual DOM, it is better to lay a little bit of virtual DOM first, understand its structure, and then look at the principle of the Diff algorithm.

When rendering the real DOM, it does not overwrite the original DOM violently, but compares the old and new vnodes (virtual nodes). If it is not the same node, delete the old one and replace it with a new one; if it is the same node, just Reuse old nodes and add attributes of new nodes.

First, understand the virtual DOM

Virtual DOM is simply that 用JS对象来模拟 DOM 结构.

Simulate DOM structure with JS objects

Example of simulating DOM structure with JS objects:

<template>
    <div id="app" class="container">
        <h1>铁锤妹妹</h1>
    </div>
</template>

The above template is converted into a JS object as follows.
Such a DOM structure is called 虚拟 DOM(Virtual Node), abbreviated vnode.

{
    
    
  tag:'div',
  props:{
    
     id:'app', class:'container' },
  children: [
    {
    
     tag: 'h1', children:'铁锤妹妹' }
  ]
}

Its expression is to convert each tag into an object, which can have three attributes: tag, props, children.

  • tag : required. Even tags can be components or functions.
  • props : optional. It is the properties and methods on this label.
  • children : optional. It is the content or child nodes of this label. If it is a text node, it is a string, and if it has child nodes, it is an array. In other words, if it is judged that children is a string, it must be a text node, and this node must have no child elements

The benefits of simulating DOM nodes with JS objects

Assuming that there are 1000 nodes that need to be updated in one operation, then the virtual DOM will not operate the Dom immediately, but save the diff content of these 1000 updates to a local one, and then put this JS对象JS object attachon the DOM tree at one time, Finally, carry out the follow-up operation, like this 避免了大量没必要的计算.

Therefore, the advantage of simulating DOM nodes with JS objects is: first reflect all the updates of the page on 虚拟DOM, so that 先操作内存中的JS对象. It is worth noting that 操作内存中的 JS对象 速度是相当快的. Then wait until all the DOM nodes are updated, and then JS对象map the last to 真实DOMand hand it over to the browser to draw.

This solves the problems of slow real DOM rendering and high performance consumption .

Why use virtual DOM?

Let's create an empty div first, and print to see all the attributes and events that come with it.

    let div = document.createElement('div')
    let props = ''
    for (let key in div) {
    
    
      props += key + ' '
    }
    console.log(props)

Print result:

insert image description here

As can be seen from the figure, the native DOM has many attributes and events, even creating an empty div will cost a lot. The point of using virtual DOM to improve performance is that when the DOM changes, the diff algorithm is compared with the DOM before the data change to calculate the DOM that needs to be changed, and then only operate on the changed DOM instead of updating the entire view.

The relationship between virtual Dom and diff algorithm

In fact, vdomit is a big concept, but diff算法a part of vdom. The core value of vdom lies in 最大程度上减少 真实DOM 的频繁更新.
vdom uses DOM to JS的方式simulate, compares the new and old virtual DOM, only updates the difference, and then operates the real DOM in batches, reducing frequent operations on the real DOM and improving performance. Then the process of comparison is diff算法. In other words, both are 包含关系, as shown in the following figure:
insert image description here

Second, understand the diff algorithm

Optimization of diff algorithm

If there are 1,000 nodes, it needs to be calculated 1,000³ times, that is, 1 billion times, which is unacceptable, so when using the Diff algorithm in Vue, it follows the strategy of 深度优先some 同层比较optimizations to calculate 最小变化.

1)只比较同一层级,不跨级比较

The Diff process only compares the DOM framed by the same color 同一层级, which simplifies the number of comparisons.
insert image description here

2) If the tag names of the same level are different, delete the old virtual DOM directly and rebuild without continuing to do in-depth comparison.比较tag标签名

insert image description here

3) If they are the same, they are also the same, it will be considered as yes , and no in-depth comparison will be continued. For example, when we write v-for, we will compare keys, and if we don’t write keys, an error will be reported. This is because the Diff algorithm needs to compare keys.比较 key
标签名key相同节点

The role of the key

It will be better understood through graphical examples:
For example, if there is a list and an element needs to be inserted in the middle of the list, what will happen? first look at a picture
insert image description here

As shown in the figure, li1 and li2 will not be re-rendered, but li3, li4, and li5 will all be re-rendered.

Because when the key or the index of the list is not used as the key, the positional relationship corresponding to each element is the index index. The result in the above figure directly leads to the insertion of the element to all the following elements, and the corresponding positional relationship occurs. Change, so all will perform update operations, this is not what we want, what we want is to render only the added element li5, without any changes to the other four elements, just reuse it in place, do not re-render.

In the case of using a unique key, the positional relationship corresponding to each element is the key. Take a look at the case of using a unique key value:

insert image description here

In this way, li3 and li4 in the figure will not be re-rendered, because the content of the elements has not changed, and the corresponding positional relationship has not changed.
This is why v-for must write the key, and it is not recommended to use the index of the array as the key in development.

in conclusion:

  • The role of the key is mainly to update the virtual DOM more efficiently, because it can find the same node very accurately, and the diff operation can be more efficient.
    If the order of the data items has changed, Vue will not move the DOM elements to match the order of the data items, but simply " 就地复用" each element here.

When is the diff algorithm executed?

1. 首次渲染When the page is opened, the patch will be called once and a new vnode will be created, and no deeper comparison will be performed.
2. Then 组件中的数据发生变化it will be triggered at the time of , setterand then through notify()the notification watcher, the corresponding watcherwill notify the update and execute the update function, which will execute renderthe function to obtain the new virtual DOM, and then perform the patchcomparison with the old virtual DOM, and calculate the minimum change, Then go to update according to this minimum change 真实的DOM, that is, view update.

3. In-depth diff algorithm source code

patch function

The core function used to compare the old and new VNodes and update the DOM.

It should be noted that the patch function will reuse the existing DOM elements and nodes as much as possible when updating the DOM, so as to improve performance. It will only update the parts that have really changed by comparing the differences between the old and new VNode, without recreating the entire DOM structure.

The main process is this:

  • If vnode does not exist and oldVnode exists, delete oldVnode. (The absence of vnode means that the component is removed or no longer needs to be rendered. In order to keep the view and data in sync, delete oldVnode)

  • If vnode exists, if oldVnode does not exist, create vnode.

  • If both exist, sameVnode()compare whether they are the same node by .

    1) If it is the same node, use patchVnode()the function to perform subsequent comparisons 节点文本变化or 子节点变化.
    2) If it is not the same node, delete the node and recreate a new node to replace it
    (for component nodes, Vue will reuse existing component instances as much as possible instead of destroying and recreating components)

// src/core/vdom/patch.ts

// 两个判断函数
export function isUndef(v: any): v is undefined | null {
    
    
  return v === undefined || v === null
}

export function isDef<T>(v: T): v is NonNullable<T> {
    
    
  return v !== undefined && v !== null
}

 return function patch(oldVnode, vnode, hydrating, removeOnly) {
    
    
   // 当新的 VNode 不存在时,如果旧的 VNode 存在,则调用旧的 VNode 的销毁钩子函数,以确保在组件更新过程中正确地执行销毁逻辑。
   // 如果新的 VNode 不存在,通常表示组件 被移除 或者 不再需要渲染。
   // 如果旧的 VNode 仍然存在,它对应的 DOM 元素需要被删除,以保持视图与数据的同步。确保不留下无用的 DOM 节点,避免内存泄漏和不必要的性能开销。
    if (isUndef(vnode)) {
    
    
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue: any[] = []

  // 如果 oldVnode 不存在的话,新的 vnode 是肯定存在的,比如首次渲染的时候
    if (isUndef(oldVnode)) {
    
    
      isInitialPatch = true
       // 就创建新的 vnode
      createElm(vnode, insertedVnodeQueue)
    } else {
    
    
    // 剩下的都是新的 vnode 和 oldVnode 都存在的话
    
    // 旧的 VNode是不是元素节点
      const isRealElement = isDef(oldVnode.nodeType)
     // 如果旧的 VNode 是真实的 DOM 元素节点 && 与新的 VNode 是同一个节点
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
    
    
        // 如果是,就用 patchVnode 对现有的根节点进行更新操作,而不是重新创建整个组件树。
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
    
    
      // 如果不是同一元素节点的话
        if (isRealElement) {
    
    
          // const SSR_ATTR = 'data-server-rendered'
          // 如果是元素节点 并且有 'data-server-rendered' 这个属性
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
    
    
          // 就是服务端渲染,删掉这个属性
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          // 就是服务端渲染的,删掉这个属性
          if (isTrue(hydrating)) {
    
    
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
    
    
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (__DEV__) {
    
    
              warn('一段很长的警告信息')
            }
          }
          // 如果不是服务端渲染的,或者混合失败,就创建一个空的注释节点替换 oldVnode
          oldVnode = emptyNodeAt(oldVnode)
        }

        // 拿到 oldVnode 的父节点
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

         // 根据新的 vnode 创建一个 DOM 节点,挂载到父节点上
        createElm(
          vnode,
          insertedVnodeQueue,
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

       // 如果新的 vnode 的根节点存在,就是说根节点被修改了,就需要遍历更新父节点
        if (isDef(vnode.parent)) {
    
    
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          // 递归更新父节点下的元素
          while (ancestor) {
    
    
           // 卸载老根节点下的全部组件
            for (let i = 0; i < cbs.destroy.length; ++i) {
    
    
              cbs.destroy[i](ancestor)
            }
            // 替换现有元素
            ancestor.elm = vnode.elm
            if (patchable) {
    
    
              for (let i = 0; i < cbs.create.length; ++i) {
    
    
                cbs.create[i](emptyNode, ancestor)
              }
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
    
    
                for (let i = 1; i < insert.fns.length; i++) {
    
    
                  insert.fns[i]()
                }
              }
            } else {
    
    
              registerRef(ancestor)
            }
            // 更新父节点
            ancestor = ancestor.parent
          }
        }

        // 如果旧节点还存在,就删掉旧节点
        if (isDef(parentElm)) {
    
    
          removeVnodes([oldVnode], 0, 0)
        // 否则直接卸载 oldVnode
        } else if (isDef(oldVnode.tag)) {
    
    
          invokeDestroyHook(oldVnode)
        }
      }
    }
    // 返回更新后的节点
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}

sameVnode function

This is the function used to judge 新旧Vnodewhether or not 同一节点.

function sameVnode(a, b) {
    
    
  return (
    a.key === b.key &&  // key 是不是一样
    a.asyncFactory === b.asyncFactory &&  // 是不是异步组件
    ((a.tag === b.tag &&  // 标签是不是一样
      a.isComment === b.isComment &&  // 是不是注释节点
      isDef(a.data) === isDef(b.data) &&  // 内容数据是不是一样
      sameInputType(a, b)) ||   // 判断 input 的 type 是不是一样
      (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))   // 判断区分异步组件的占位符否存在
  )
}

patchVnode function

This is a function that will be executed when the new vnode and oldVnode are the same node, mainly to compare the changes of node text or child nodes.

The main process is this:

  • If the reference addresses of oldVnode and vnode are the same, it means that the node has not changed and returns directly.

  • If the isAsyncPlaceholder of oldVnode exists, skip the check of the asynchronous component and return directly.

  • If both oldVnode and vnode are static nodes && have the same key && vnode is a node controlled by the clone node || v-once command, copy oldVnode.elm and oldVnode.child to vnode, and then return.

  • If vnode is neither a text node nor a comment

    1) If both oldVnode and vnode have child nodes, and 子节点不一样, call updateChildren()the function to update the child nodes.
    2) If only vnodehas child nodes, call addVnodes()create child nodes.
    3) If only oldVnodehas a child node, call to removeVnodes()delete the child node.
    4) If oldVnodeis a text node, clear it.

  • If vnode is a text node but the text content is different from oldVnode, update the text

 function patchVnode(
    oldVnode,  // 旧的虚拟 DOM 节点
    vnode,   // 新的虚拟 DOM 节点
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly?: any
  ) {
    
    
  // 新老节点引用地址是一样的,return 返回
  // 比如 props 没有改变的时候,子组件就不做渲染,直接复用
    if (oldVnode === vnode) {
    
    
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
    
    
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    const elm = (vnode.elm = oldVnode.elm)
    
    // 如果当前节点是注释或 v-if 的,或者是异步函数,就跳过检查异步组件
    if (isTrue(oldVnode.isAsyncPlaceholder)) {
    
    
      if (isDef(vnode.asyncFactory.resolved)) {
    
    
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
    
    
        vnode.isAsyncPlaceholder = true
      }
      return
    }

    // 当前节点是静态节点的时候,key 也一样,并且vnode 是克隆节点,或者有 v-once 的时候,就直接赋值返回
    if (
      isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
    
    
      vnode.componentInstance = oldVnode.componentInstance
      return
    }

    let i
    const data = vnode.data
    if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {
    
    
      i(oldVnode, vnode)
    }
    
    // 获取子元素列表
    const oldCh = oldVnode.children
    const ch = vnode.children
    
    if (isDef(data) && isPatchable(vnode)) {
    
    
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef((i = data.hook)) && isDef((i = i.update))) i(oldVnode, vnode)
    }
    
    // 如果新节点不是文本节点,也就是说有子节点
    if (isUndef(vnode.text)) {
    
    
    // 如果新旧节点都有子节点
      if (isDef(oldCh) && isDef(ch)) {
    
    
      // 但是子节点不一样,就调用 updateChildren 函数,对比子节点
        if (oldCh !== ch)
          updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
    
    
      // 如果只有新节点有子节点的话,新增子节点
        
        // 如果 旧节点 是文本节点,表示它没有子节点,就清空
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // 新增 子节点
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
    
    
       // 如果只有 旧节点 有子节点,就删除
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
    
    
        // 如果旧节点是文本节点,就清空
        nodeOps.setTextContent(elm, '')
      }
    // 新老节点都是文本节点,且文本不一样,就更新文本
    } else if (oldVnode.text !== vnode.text) {
    
    
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
    
    
      if (isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode)
    }
  }

updateChildren function

This is a new function that both vnodeand oldVnodehave child nodes, and 子节点不一样compare child nodes when .

这个函数 很关键,很关键!

For example, now there are two child node lists to compare, the main process of the comparison is as follows:

Loop through two lists, the loop stop condition is: the start pointer startIdx of one of the lists coincides with the end pointer endIdx .
The content of the loop is:

  • new head vs old head
  • New Tail vs. Old Tail
  • New tail vs old head
  • New head vs. old tail

As long as one of the above four judgments is equal, call patchVnode()the comparison node text change or child node change, and then 移动对比的下标continue the next round of cyclic comparison.

If none of the above four situations are hit, it will be used 循环to search, and the key of the new node will be used to search in the old children.

  • If not found, create a new node.

  • If found, compare whether the label is the same node.

    1) If it is the same node, call to pathVnode()perform subsequent comparison, then insert this node 老的开始前面, and move the new start subscript to continue the next round of cyclic comparison.
    2) If it is not the same node, create a new node.

  • If the old vnode has been traversed first, add the nodes that the new vnode has not traversed.

  • If the new vnode has been traversed first, it means that there are remaining nodes in the old node, and the nodes not traversed by the old vnode will be deleted.

Why are there head-to-tail, tail-to-head operations?

  • Head-to-tail and tail-to-head are an optimization strategy of the Diff algorithm, the purpose is to minimize the cost of re-rendering by re-rendering existing DOM nodes as much as possible复用 .
  • The head-to-tail operation refers to comparing the node pairs at the beginning and end positions in the old and new node lists, and then moving the comparison step by step to the inside. The reason for this is that in many cases, the change of nodes mainly occurs at the head and tail of the list, while the nodes in the middle are relatively stable. Through the comparison of the first and last nodes, unnecessary node movement and update can be avoided, and only the newly added or deleted nodes need to be inserted or deleted.
  • Tail-to-head operates similarly to head-to-tail.
  function updateChildren(
    parentElm,
    oldCh,
    newCh,
    insertedVnodeQueue,
    removeOnly
  ) {
    
    
    let oldStartIdx = 0  // 老 vnode 遍历的开始下标
    let newStartIdx = 0  // 新 vnode 遍历的开始下标
    let oldEndIdx = oldCh.length - 1   // 老 vnode 遍历的结束下标
    let oldStartVnode = oldCh[0]   // 老 vnode 列表第一个子元素
    let oldEndVnode = oldCh[oldEndIdx]   // 老 vnode 列表最后一个子元素
    let newEndIdx = newCh.length - 1  // 新 vnode 遍历的结束下标
    let newStartVnode = newCh[0]  // 新 vnode 列表第一个子元素
    let newEndVnode = newCh[newEndIdx]   // 老 vnode 列表最后一个子元素
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    const canMove = !removeOnly

    if (__DEV__) {
    
    
      checkDuplicateKeys(newCh)
    }
    
    // 循环,规则是开始指针向右移动,结束指针向左移动
    // 当开始 和 结束的 指针重合 的时候就结束循环
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    
    
      if (isUndef(oldStartVnode)) {
    
    
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
    
    
        oldEndVnode = oldCh[--oldEndIdx]
        
       // 老的头和新的头对比
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
    
    
      // 是同一节点 递归调用 继续对比这两个节点的内容和子节点
        patchVnode(
          oldStartVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        )
        // 然后把指针后移一位,从前往后依次对比
        // 比如第一次对比两个列表[0],然后对比[1]...,后面同理
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
        
       // 老结束和新结束对比
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
    
    
        patchVnode(
          oldEndVnode,
          newEndVnode,
          insertedVnodeQueue,
          newCh,
          newEndIdx
        )
         // 然后把指针前移一位,从后往前依次对比
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]

      // 老开始和新结束对比
      } else if (sameVnode(oldStartVnode, newEndVnode)) {
    
    
        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)) {
    
    
        patchVnode(
          oldEndVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        )
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        // 老的列表从后往前取值,新的列表从前往后取值,然后对比
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
        
      // 以上四种情况都没有命中的情况
      } else {
    
    
        if (isUndef(oldKeyToIdx))
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
         // 拿到新开始的 key,在老的 children 里去找有没有某个节点有这个 key
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

       // 新的 children 里有,可是没有在老的 children 里找到对应的元素
        if (isUndef(idxInOld)) {
    
    
          // 创建新的元素
          createElm(
            newStartVnode,
            insertedVnodeQueue,
            parentElm,
            oldStartVnode.elm,
            false,
            newCh,
            newStartIdx
          )
        } else {
    
    
        // 在老的 children 里找到了对应的元素
          vnodeToMove = oldCh[idxInOld]
          // 判断是否是同一个元素
          if (sameVnode(vnodeToMove, newStartVnode)) {
    
    
          // 是同一节点 递归调用 继续对比这两个节点的内容和子节点
            patchVnode(
              vnodeToMove,
              newStartVnode,
              insertedVnodeQueue,
              newCh,
              newStartIdx
            )
            oldCh[idxInOld] = undefined
            canMove &&
              nodeOps.insertBefore(
                parentElm,
                vnodeToMove.elm,
                oldStartVnode.elm
              )
          } else {
    
    
            // 不同的话,就创建新元素
            createElm(
              newStartVnode,
              insertedVnodeQueue,
              parentElm,
              oldStartVnode.elm,
              false,
              newCh,
              newStartIdx
            )
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    
   // 说明老的 vnode 先遍历完
    if (oldStartIdx > oldEndIdx) {
    
    
    // 就添加从 newStartIdx 到 newEndIdx 之间的节点
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(
        parentElm,
        refElm,
        newCh,
        newStartIdx,
        newEndIdx,
        insertedVnodeQueue
      )

    // 否则就说明新的 vnode 先遍历完
    } else if (newStartIdx > newEndIdx) {
    
    
    // 就删除老的 vnode 里没有遍历的结点
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }

Summarize

1. The parsing process of virtual DOM

First analyze the DOM tree structure to be inserted into the document, and use js对象to express it, such as an element object, including TagName, propsand Childrenthese attributes. Then js对象树save this, and finally insert the DOM fragment into the document.

When the state of the page changes and the structure of the DOM of the page needs to be adjusted, an object tree is first rebuilt according to the changed state, and then the and are performed to record the differences between the 新的对象树two 旧的对象树trees 比较.

Finally, apply the recorded differences to 真正的DOM树, so that the view is updated .

2. The principle of diff algorithm

When comparing the old and new virtual dom:

First, compare the nodes themselves, and sameVnode()judge whether they are the same node by .

  • If it is not the same node, delete the node and create a new node to replace it.
  • If it is the same node, proceed patchVnode()to determine how to process the child nodes of this node.

First judge the situation that one party has child nodes and the other party has no child nodes.

1) If the new children has child nodes, call to addVnodes()create new child nodes.
2) If the new children has no child nodes, call to removeVnodes()delete the old child nodes.

If there are all child nodes, but the child nodes are different, then proceed updateChildren()to determine how to operate on the child nodes of these old and new nodes (diff core).

When matching, identical child nodes are found, and patchVnode()the function is called recursively to further compare and update those child nodes .

In diff, only 同层the child nodes are compared, and the cross-level node comparison is abandoned, so that the time complexity is reduced from O(n3) to O(n), that is, only when the old and new children are multiple child nodes. Use the core Diff algorithm for same-level comparisons.

You can refer to:
Internet celebrity virtual DOM in the interview, how much do you know? In-depth interpretation of diff algorithm,
in-depth explanation of virtual DOM and Diff algorithm, and the difference between Vue2 and Vue3

Guess you like

Origin blog.csdn.net/weixin_45811256/article/details/131815014