Vue中的Diff算法实现过程

news/2024/7/10 0:38:36 标签: vue, vue.js, node.js, javascript

目录

    • 1.Diff的由来?
    • 2.如何实现?

1.Diff的由来?

Vue利用双向绑定原理,实现了视图层和数据层的同时更新,在数据层发生变化的时候利用虚拟DOM去更新对应的DOM树,那么新DOM树和旧DOM树如何去比对,DOM-diff主要就是用来做这个事情,将新旧DOM树进行比对,更新。

2.如何实现?

vue中的DOM-diff 也叫patch ,意思就是打补丁,核心思想就是将旧的VNode修补得到新的VNode,然后完成渲染。总而言之,DOM-diff其实就干了三件事情

  1. 创建节点:新的VNode中有而旧的oldVNode中没有,就在旧的oldVNode中创建。
  2. 删除节点:新的VNode中没有而旧的oldVNode中有,就从旧的oldVNode中删除。
  3. 更新节点:新的VNode和旧的oldVNode中都有,就以新的VNode为准,更新旧的oldVNode。

1.创建节点
VNode类中具有6种类型的节点,但是实际只有三种类型的节点能够被创建

  1. 元素节点
  2. 文本节点
  3. 注释节点
    在创建节点的时候如何判断节点的类型,其实非常容易判断的,看看在源码中是如何判断的
javascript">function createElm (vnode, parentElm, refElm) {
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
           vnode.elm = nodeOps.createElement(tag, vnode)   // 创建元素节点
        createChildren(vnode, children, insertedVnodeQueue) // 创建元素节点的子节点
        insert(parentElm, vnode.elm, refElm)       // 插入到DOM中
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text)  // 创建注释节点
      insert(parentElm, vnode.elm, refElm)           // 插入到DOM中
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)  // 创建文本节点
      insert(parentElm, vnode.elm, refElm)           // 插入到DOM中
    }
  }

解析:

  • 1.首先判断该节点是不是元素节点吗,只需要判断VNode节点中是不是含有tag标签即可,如果存在tag标签,那么该节点一定是元素节点调用createElement方法创建元素节点
  • 2.判断是不是注释节点,因为在VNode类中对于注释节点有一个特殊的标记isComment,我们只需要判断isComment是不是true,如果是则调用createComment方法创建注释节点
  • 3.使用排除法,如果以上两个条件都不满足那么他就一定是文本节点,则调用createTextNode方法创建文本节点,再插入到DOM中。

2.删除节点
如果新的虚拟DOM树上不具有的节点,但是旧的DOM树上却有,那么我们就需要把这些节点从旧的虚拟DOM中删除,删除节点我们只需要调用删除节点父元素的removeChild方法

javascript">function removeNode (el) {
    const parent = nodeOps.parentNode(el)  // 获取父节点
    if (isDef(parent)) {
      nodeOps.removeChild(parent, el)  // 调用父节点的removeChild方法
    }
  }

3.更新节点
更新节点之前我们需要将新DOM树和旧DOM树进行一个比对,当然在比对的过程中一定会存在很多的静态节点
何为静态节点?

  • 静态节点其实就是我们写死的一些文字,没有任何可变的变量,不管数据如何变化,都和这个节点无关,这样的节点就成为DOM树上的静态节点

源码

javascript">// 更新节点function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// vnode与oldVnode是否完全一样?若是,退出程序
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm

// vnode与oldVnode是否都是静态节点?若是,退出程序
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
return
}

const oldCh = oldVnode.children
const ch = vnode.children
// vnode有text属性?若没有:
if (isUndef(vnode.text)) {
// vnode的子节点与oldVnode的子节点是否都存在?
if (isDef(oldCh) && isDef(ch)) {
// 若都存在,判断子节点是否相同,不同则更新子节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
}
// 若只有vnode的子节点存在
else if (isDef(ch)) {
/**
* 判断oldVnode是否有文本?
* 若没有,则把vnode的子节点添加到真实DOM中
* 若有,则清空Dom中的文本,再把vnode的子节点添加到真实DOM中
*/
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
}
// 若只有oldnode的子节点存在
else if (isDef(oldCh)) {
// 清空DOM中的子节点
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
}
// 若vnode和oldnode都没有子节点,但是oldnode中有文本
else if (isDef(oldVnode.text)) {
// 清空oldnode文本
nodeOps.setTextContent(elm, '')
}
// 上面两个判断一句话概括就是,如果vnode中既没有text,也没有子节点,那么对应的oldnode中有什么就清空什么
}
// 若有,vnode的text属性与oldVnode的text属性是否相同?
else if (oldVnode.text !== vnode.text) {
// 若不相同:则用vnode的text替换真实DOM的文本
nodeOps.setTextContent(elm, vnode.text)
}}

解析
当然我们在更新之前也会针对不通类型的节点提出不同的方案

  • 1.文本节点

    如果新旧DOM树只有文本节点,那么我们只需要改变里面的纯文本调用setTextNode方法

  • 2.元素节点
    如果VNode是元素节点,则又细分以下两种情况:

    • 该节点包含子节点
      • 如果新的节点内包含了子节点,那么此时要看旧的节点是否包含子节点,如果旧的节点里也包含了子节点,那就需要递归对比更新子节点;如果旧的节点里不包含子节点,那么这个旧节点有可能是空节点或者是文本节点,如果旧的节点是空节点就把新的节点里的子节点创建一份然后插入到旧的节点里面,如果旧的节点是文本节点,则把文本清空,然后把新的节点里的子节点创建一份然后插入到旧的节点里面。
    • 该节点不包含子节点
      • 如果该节点不包含子节点,同时它又不是文本节点,那就说明该节点是个空节点,那就好办了,不管旧节点之前里面都有啥,直接清空即可。

http://www.niftyadmin.cn/n/1319083.html

相关文章

ASP.NET MVC 如何实现头压缩

网页的头部压缩在页面体积大的情况下非常有必要做,它会使页面体积有一个明显的减小,同时加到网页从服务端下载到客户端的速度,以下是我做的一个测试: 没有使用头压缩时: 使用了头压缩后: 我们可以看到&…

JDK1.5 生产消费者

ArrayBlockingQueue: 一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作则…

什么是vue?如何理解双向数据绑定?Vue2.0和Vue3.0实现双向绑定的区别

1.何为Vue? Vue是一套用于构建单页面应用的JavaScript框架,它的核心采用MVVM框架进行搭建,自底向下增量开发,主要的特点就是实现了数据的双向绑定,通过渐进式的开发模式减小项目包体积,采用模块化开发的思…

WPF附加属性

1、定义:一个属性原来不属于某个对象,但由于某种需求而被后来附加上去。附加属性的本质是依赖属性。 2、作用:将属性与数据类型解耦,让数据类型的设计的更加灵活。 3、举例:Human,School。Human中的一个人&…

JDK1.5 Condition接口

Condition 将 Object 监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 syn…

Vue中常见的指令介绍以及那些我踩过的坑

这篇文章继续给各位前端程序员们介绍Vue相关的技术点。这篇文章主要分为以下几个部分: 何为指令? 指令的定义 常见的指令有哪些 指令的定义? 指令,说到指令其实就是操作DOM树的一些内置指令,大家都知道Vue采用虚拟…

Oracle 方法

语法&#xff1a; CREATE OR REPLACE FUNCTION <function_name> [(<parameters>)] RETURN <datatype> IS [declare section] BEGIN[<statement(s)>] RETURN <expression>; [EXCEPTION <exception handler(s);] END [<function_name>]…

git分支控制心得

1.git 删除本地分支 git branch -D dev 2. git 查看本地分支 git branch 3. git 查看本地分支和远程分支的区别 git branch -a 4. git 删除远程分支 git push origin -delete dev 5. git 切换分支 git checkout dev 6.git 删除远程不存在的本地分支 git fetch --prune