vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue虚拟DOM与Diff算法

一文详解Vue中的虚拟DOM与Diff算法

作者:沐渃清澄

vue中的diff算法时常是面试过程中的考点,本文将为大家讲解何为diff以及diff算法的实现过程,那么在了解diff之前,我们需要先了解虚拟DOM是什么,需要的朋友可以参考下

虚拟DOM

虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的DOM与之保持同步。具体来说,虚拟 DOM 是由一系列的 JavaScript 对象组成的树状结构,每个对象代表着一个DOM元素,包括元素的标签名、属性、子节点等信息。虚拟 DOM 中的每个节点都是一个 JavaScript 对象,它们可以轻松地被创建、更新和销毁,而不涉及到实际的DOM操作。

主要作用

虚拟 DOM 的主要作用是在数据发生变化时,通过与上一次渲染的虚拟 DOM 进行对比,找出发生变化的部分,并最小化地更新实际 DOM。这种方式可以减少实际 DOM 操作的次数,从而提高页面渲染的性能和效率。
总的来说,虚拟 DOM 是一种用 JavaScript 对象模拟真实 DOM 结构和状态的技术,它通过在内存中操作虚拟 DOM 树来减少实际 DOM 操作,从而提高页面的性能和用户体验。

虚拟DOM树

顾名思义,也就是一个虚拟 DOM 作为根节点,包含有一个或多个的子虚拟 DOM。

Diff

在 Vue 3 中,diff(差异比较)是指在进行虚拟 DOM 更新时,对比新旧虚拟 DOM 树的差异,然后只对实际发生变化的部分进行更新,以尽可能地减少对真实 DOM 的操作,提高页面的性能和效率。diff整体策略为:深度优先,同层比较。也就是说,比较只会在同层级进行, 不会跨层级比较;比较的过程中,循环从两边向中间收拢。

流程解析

Diff 算法的实现流程可以概括为以下几个步骤:

源码解析

在源码中patchVnode是diff发生的地方,下面是patchVnode的源码:

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  // 如果新旧节点一致,什么都不做
  if (oldVnode === vnode) {
    return
  }

  // 让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化
  const elm = vnode.elm = oldVnode.elm

  // 异步占位符
  if (isTrue(oldVnode.isAsyncPlaceholder)) {
    if (isDef(vnode.asyncFactory.resolved)) {
      hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
    } else {
      vnode.isAsyncPlaceholder = true
    }
    return
  }
  // 如果新旧都是静态节点,并且具有相同的key
  // 当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上
  // 也不用再有其他操作
  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)
  }
  // 如果vnode不是文本节点或者注释节点
  if (isUndef(vnode.text)) {
    // 并且都有子节点
    if (isDef(oldCh) && isDef(ch)) {
      // 并且子节点不完全一致,则调用updateChildren
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

      // 如果只有新的vnode有子节点
    } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      // elm已经引用了老的dom节点,在老的dom节点上添加子节点
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

      // 如果新vnode没有子节点,而vnode有子节点,直接删除老的oldCh
    } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1)

      // 如果老节点是文本节点
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '')
    }

    // 如果新vnode和老vnode是文本节点或注释节点
    // 但是vnode.text != oldVnode.text时,只需要更新vnode.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)
  }
}

以上代码主要就是用于比较新旧虚拟 DOM 节点并进行更新。让我逐步解释这个函数的实现:

Diff算法示例

下面是一个详细的例子,假设有以下两个虚拟 DOM 树,我们将对它们进行 diff 算法的比较:

旧的虚拟 DOM 树:

{
  type: 'div',
  props: { id: 'container' },
  children: [
    { type: 'p', props: { class: 'text' }, children: ['old Dom'] },
    { type: 'button', props: { disabled: true }, children: ['click'] }
  ]
}

新的虚拟 DOM 树:

{
  type: 'div',
  props: { id: 'container' },
  children: [
    { type: 'p', props: { class: 'text' }, children: ['new DOM'] },
    { type: 'button', props: { disabled: false }, children: ['click'] },
    { type: 'span', props: { class: 'msg' }, children: ['msg'] }
  ]
}

Diff算法执行:

最终结果:

    {
  type: 'div',
  props: { id: 'container' },
  children: [
    { type: 'p', props: { class: 'text' }, children: ['new DOM'] },
    { type: 'button', props: { disabled: false }, children: ['click'] },
     { type: 'span', props: { class: 'msg' }, children: ['msg'] }
  ]
}

结语

总的来说,Diff 算法的核心思想是Diff就是将新老虚拟DOM的不同点找到并生成一个补丁,并根据这个补丁生成更新操作,以最小化对实际 DOM 的操作,提高页面渲染的性能和效率。通过深度优先、同层比较的策略,Diff 算法能够高效地处理虚拟 DOM 树的更新,使得页面在数据变化时能够快速响应并更新对应的视图。

以上就是一文详解Vue中的虚拟DOM与Diff算法的详细内容,更多关于Vue虚拟DOM与Diff算法的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文