Vue源码系列讲解——虚拟DOM篇【二】(Vue中的DOM-Diff)

目录

1. 前言

2. patch

3. 创建节点

4. 删除节点

5. 更新节点

6. 总结

1. 前言

在上一篇文章介绍VNode的时候我们说了,VNode最大的用途就是在数据变化前后生成真实DOM对应的虚拟DOM节点,然后就可以对比新旧两份VNode,找出差异所在,然后更新有差异的DOM节点,最终达到以最少操作真实DOM更新视图的目的。而对比新旧两份VNode并找出差异的过程就是所谓的DOM-Diff过程。DOM-Diff算法是整个虚拟DOM的核心所在,那么接下来,我们就以源码出发,深入研究一下Vue中的DOM-Diff过程是怎样的。

2. patch

Vue中,把 DOM-Diff过程叫做patch过程。patch,意为“补丁”,即指对旧的VNode修补,打补丁从而得到新的VNode,非常形象哈。那不管叫什么,其本质都是把对比新旧两份VNode的过程。我们在下面研究patch过程的时候,一定把握住这样一个思想:所谓旧的VNode(即oldVNode)就是数据变化之前视图所对应的虚拟DOM节点,而新的VNode是数据变化之后将要渲染的新的视图所对应的虚拟DOM节点,所以我们要以生成的新的VNode为基准,对比旧的oldVNode,如果新的VNode上有的节点而旧的oldVNode上没有,那么就在旧的oldVNode上加上去;如果新的VNode上没有的节点而旧的oldVNode上有,那么就在旧的oldVNode上去掉;如果某些节点在新的VNode和旧的oldVNode上都有,那么就以新的VNode为准,更新旧的oldVNode,从而让新旧VNode相同。

可能你感觉有点绕,没关系,我们在说的通俗一点,你可以这样理解:假设你电脑上现在有一份旧的电子版文档,此时老板又给了你一份新的纸质板文档,并告诉你这两份文档内容大部分都是一样的,让你以新的纸质版文档为准,把纸质版文档做一份新的电子版文档发给老板。对于这个任务此时,你应该有两种解决方案:一种方案是不管它旧的文档内容是什么样的,统统删掉,然后对着新的纸质版文档一个字一个字的敲进去,这种方案就是不用费脑,就是受点累也能解决问题。而另外一种方案是以新的纸质版文档为基准,对比看旧的电子版文档跟新的纸质版文档有什么差异,如果某些部分在新的文档里有而旧的文档里没有,那就在旧的文档里面把这些部分加上;如果某些部分在新的文档里没有而旧的文档里有,那就在旧的文档里把这些部分删掉;如果某些部分在新旧文档里都有,那就对比看有没有需要更新的,最后在旧的文档里更新一下,最终达到把旧的文档变成跟手里纸质版文档一样,完美解决。

对比以上两种方案,显然你和Vue一样聪明,肯定会选择第二种方案。第二种方案里的旧的电子版文档对应就是已经渲染在视图上的oldVNode,新的纸质版文档对应的是将要渲染在视图上的新的VNode。总之一句话:以新的VNode为基准,改造旧的oldVNode使之成为跟新的VNode一样,这就是patch过程要干的事

说了这么多,听起来感觉好像很复杂的样子,其实不然,我们仔细想想,整个patch无非就是干三件事:

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

OK,到这里,你就对Vue中的patch过程理解了一半了,接下来,我们就逐个分析,看Vue对于以上三件事都是怎么做的。

3. 创建节点

在上篇文章中我们分析了,VNode类可以描述6种类型的节点,而实际上只有3种类型的节点能够被创建并插入到DOM中,它们分别是:元素节点、文本节点、注释节点。所以Vue在创建节点的时候会判断在新的VNode中有而旧的oldVNode中没有的这个节点是属于哪种类型的节点,从而调用不同的方法创建并插入到DOM中。

其实判断起来也不难,因为这三种类型的节点其特点非常明显,在源码中是怎么判断的:

// 源码位置: /src/core/vdom/patch.js
function createElm (vnode, parentElm, refElm) {const data = vnode.dataconst children = vnode.childrenconst tag = vnode.tagif (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中}}

从上面代码中,我们可以看出:

  • 判断是否为元素节点只需判断该VNode节点是否有tag标签即可。如果有tag属性即认为是元素节点,则调用createElement方法创建元素节点,通常元素节点还会有子节点,那就递归遍历创建所有子节点,将所有子节点创建好之后insert插入到当前元素节点里面,最后把当前元素节点插入到DOM中。
  • 判断是否为注释节点,只需判断VNodeisComment属性是否为true即可,若为true则为注释节点,则调用createComment方法创建注释节点,再插入到DOM中。
  • 如果既不是元素节点,也不是注释节点,那就认为是文本节点,则调用createTextNode方法创建文本节点,再插入到DOM中。

代码中的nodeOpsVue为了跨平台兼容性,对所有节点操作进行了封装,例如nodeOps.createTextNode()在浏览器端等同于document.createTextNode()

以上就完成了创建节点的操作,其完整流程图如下: 

4. 删除节点

如果某些节点再新的VNode中没有而在旧的oldVNode中有,那么就需要把这些节点从旧的oldVNode中删除。删除节点非常简单,只需在要删除节点的父元素上调用removeChild方法即可。源码如下:

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

5. 更新节点

创建节点和删除节点都比较简单,而更新节点就相对较为复杂一点了,其实也不算多复杂,只要理清逻辑就能理解了。

更新节点就是当某些节点在新的VNode和旧的oldVNode中都有时,我们就需要细致比较一下,找出不一样的地方进行更新。

介绍更新节点之前,我们先介绍一个小的概念,就是什么是静态节点?我们看个例子:

<p>我是不会变化的文字</p>

上面这个节点里面只包含了纯文字,没有任何可变的变量,这也就是说,不管数据再怎么变化,只要这个节点第一次渲染了,那么它以后就永远不会发生变化,这是因为它不包含任何变量,所以数据发生任何变化都与它无关。我们把这种节点称之为静态节点。

OK,有了这个概念以后,我们开始更新节点。更新节点的时候我们需要对以下3种情况进行判断并分别处理:

  1. 如果VNodeoldVNode均为静态节点

    我们说了,静态节点无论数据发生任何变化都与它无关,所以都为静态节点的话则直接跳过,无需处理。

  2. 如果VNode是文本节点

    如果VNode是文本节点即表示这个节点内只包含纯文本,那么只需看oldVNode是否也是文本节点,如果是,那就比较两个文本是否不同,如果不同则把oldVNode里的文本改成跟VNode的文本一样。如果oldVNode不是文本节点,那么不论它是什么,直接调用setTextNode方法把它改成文本节点,并且文本内容跟VNode相同。

  3. 如果VNode是元素节点

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

    • 该节点包含子节点

      如果新的节点内包含了子节点,那么此时要看旧的节点是否包含子节点,如果旧的节点里也包含了子节点,那就需要递归对比更新子节点;如果旧的节点里不包含子节点,那么这个旧节点有可能是空节点或者是文本节点,如果旧的节点是空节点就把新的节点里的子节点创建一份然后插入到旧的节点里面,如果旧的节点是文本节点,则把文本清空,然后把新的节点里的子节点创建一份然后插入到旧的节点里面。

    • 该节点不包含子节点

      如果该节点不包含子节点,同时它又不是文本节点,那就说明该节点是个空节点,那就好办了,不管旧节点之前里面都有啥,直接清空即可。

OK,处理完以上3种情况,更新节点就算基本完成了,接下来我们看下源码中具体是怎么实现的,源码如下:

// 更新节点
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.childrenconst 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)}
}

上面代码里注释已经写得很清晰了,接下来我们画流程图来梳理一下整个过程,流程图如下:

 

通过对照着流程图以及代码,相信更新节点这部分逻辑你很容易就能理解了。

另外,你可能注意到了,如果新旧VNode里都包含了子节点,那么对于子节点的更新在代码里调用了updateChildren方法,而这个方法的逻辑到底是怎样的我们放在下一篇文章中展开学习。

6. 总结

在本篇文章中我们介绍了Vue中的DOM-Diff算法:patch过程。我们先介绍了算法的整个思想流程,然后通过梳理算法思想,了解了整个patch过程干了三件事,分别是:创建节点,删除节点,更新节点。并且对每件事情都对照源码展开了细致的学习,画出了其逻辑流程图。另外对于更新节点中,如果新旧VNode里都包含了子节点,我们就需要细致的去更新子节点,关于更新子节点的过程我们在下一篇文章中展开学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2775665.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB知识点:使用逻辑值修改或删除矩阵元素

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章 3.4.4 逻辑运算 3.4.4.3 使用逻辑值修改或删…

Elasticsearch(四)

是这样的前面的几篇笔记&#xff0c;感觉对我没有形成知识体系&#xff0c;感觉乱糟糟的&#xff0c;只是大概的了解了一些基础知识&#xff0c;仅此而已&#xff0c;而且对于这技术栈的学习也是为了在后面的java开发使用&#xff0c;但是这里的API学的感觉有点乱&#xff01;然…

熔断机制解析:如何用Hystrix保障微服务的稳定性

微服务与系统的弹性设计 大家好,我是小黑,在讲Hystrix之前,咱们得先聊聊微服务架构。想象一下,你把一个大型应用拆成一堆小应用,每个都负责一部分功能,这就是微服务。这样做的好处是显而易见的,更新快,容错性强,每个服务可以独立部署,挺美的对吧?但是,问题也随之而…

PKI - 借助Nginx 实现Https 服务端单向认证、服务端客户端双向认证

文章目录 Openssl操系统默认的CA证书的公钥位置Nginx Https 自签证书Nginx Https 使用CA签发证书客户端使用自签证书供服务端验证客户端使用 根证书 签发客户端证书 供服务端验证 Openssl https://www.openssl.net.cn/ openssl是一个功能丰富且自包含的开源安全工具箱。 它提…

放假--寒假自学版 day1(补2.5)

fread 函数&#xff1a; 今日练习 C语言面试题5道~ 1. static 有什么用途&#xff1f;&#xff08;请至少说明两种&#xff09; 1) 限制变量的作用域 2) 设置变量的存储域 2. 引用与指针有什么区别&#xff1f; 1) 引用必须被初始化&#xff0c;指针不必。 2) 引用初始…

无心剑七绝《龙年大吉》

七绝龙年大吉 龙腾五岳九州圆 年吼佳音万里传 大漠苍鹰华夏梦 吉人天相铸奇缘 2024年2月8日 平水韵一先平韵 这首藏头七绝《龙年大吉》是无心剑为2024年春节所创作的诗作。2024年是农历的甲辰年&#xff0c;即龙年。在中国传统文化中&#xff0c;龙是吉祥的象征&#xff0c;代表…

PSM-Net根据Stereo图像生成depth图像

一、新建文件夹 在KITTI数据集下新建depth_0目录 二、激活anaconda环境 conda activate pt14py37三、修改submission.py文件 3.1 KITTI数据集路径 parser.add_argument(--datapath, default/home/njust/KITTI_DataSet/00/, helpselect model)3.2 深度图像输出路径 save…

【复现】九思OA系统 SQL注入漏洞_43

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 九思软件自主研发的iThink协同OA办公自动化系统是面向中高端企业、政府机关和事业单位、等大型企业的协同办公软件&#xff0c;面…

pythn-scipy 查漏补缺

1. 2. 3. 4. 5. 6. 7. 8. 9. 偏度 skewness&#xff0c;峰度 kurtosis

TS学习与实践

文章目录 学习资料TypeScript 介绍TypeScript 是什么&#xff1f;TypeScript 增加了什么&#xff1f;TypeScript 开发环境搭建 基本类型编译选项类声明属性属性修饰符getter 与 setter方法static 静态方法实例方法 构造函数继承 与 super抽象类接口interface 定义接口implement…

Linux操作系统基础(二):Linux操作系统概述

文章目录 Linux操作系统概述 一、Linux起源 二、Linux 的含义 三、Linux发行版 Linux操作系统概述 一、Linux起源 Linux创始人——林纳斯 托瓦兹 Linux 诞生于1991年&#xff0c;作者上大学期间实现的 Linux的特点&#xff1a;开源、免费、拥有最为庞大的源码贡献者 …

nginx+flask+Gunicorn反代理服务拿不到真实IP的解决

背景 本人在宝塔linux环境&#xff0c;要部署flask的简单后端并且用Ngnix反代理&#xff0c;用Gunicorn框架部署。&#xff08;o(╥﹏╥)o中间磕磕绊绊总算部署上去了&#xff0c;需要了解Gunicorn怎么部署的朋友&#xff0c;评论区留言&#xff0c;我加补一篇介绍&#xff09;…

VuePress + Travis CI + Github Pages 全自动上线文档

整体思路 1.Github 创建项目&#xff0c;本地创建切换到 docs 分支&#xff0c;通过 VuePress 构建文档项目&#xff08;写一些文档&#xff09;&#xff0c;上传至 Github。 2.Travis CI 自动 clone 后安装依赖、编译、上传至 Github master 分支。 3.通过 GitHub Pages 功…

[UI5 常用控件] 08.Wizard,NavContainer

文章目录 前言1. Wizard1.1 基本结构1.2 属性1.2.1 Wizard&#xff1a;complete1.2.2 Wizard&#xff1a;finishButtonText1.2.3 Wizard&#xff1a;currentStep1.2.4 Wizard&#xff1a;backgroundDesign1.2.5 Wizard&#xff1a;enableBranching1.2.6 WizardStep&#xff1a;…

【网络攻防实验】【北京航空航天大学】【实验一、入侵检测系统(Intrusion Detection System, IDS)实验】

实验一、入侵检测系统实验 1、 虚拟机准备 本次实验使用1台 Kali Linux 虚拟机和1台 Windows XP 虚拟机,虚拟化平台选择 Oracle VM VirtualBox,如下图所示。 2、 Snort环境搭建 实验前,先确保Kali Linux虚拟机能够访问外网,将网络模式设置为“网络地址转换”: 2.1 安装…

【flink状态管理(三)】StateBackend的整体设计、StateBackend创建说明

文章目录 一. 状态后端概述二. StateBackend的整体设计1. 核心功能2. StateBackend的UML3. 小结 三. StateBackend的加载与初始化1. StateBackend创建概述2. StateBackend创建过程 一. 状态后端概述 StateBackend作为状态存储后端&#xff0c;提供了创建和获取KeyedStateBacke…

真正免费的文件恢复软件easyrecovery2024中文版

easyrecovery数据恢复软件是一款广受好评的数据恢复工具&#xff0c;它能够有效地帮助用户恢复各种类型的文件。无论是照片、视频、音乐还是文档&#xff0c;都能轻松地找回这些重要文件。操作安全、用户可自主操作的数据恢复方案&#xff0c;它支持从各种各样的存储介质恢复删…

CTFshow web(命令执行 41-44)

web41 <?php /* # -*- coding: utf-8 -*- # Author: 羽 # Date: 2020-09-05 20:31:22 # Last Modified by: h1xa # Last Modified time: 2020-09-05 22:40:07 # email: 1341963450qq.com # link: https://ctf.show */ if(isset($_POST[c])){ $c $_POST[c]; if(!p…

申请SSL证书怎么进行域名验证?域名验证的三种方式

SSL证书是用于加密和保护Web服务器和浏览器之间通信的数字证书&#xff0c;在申请SSL证书时&#xff0c;为了防止域名被冒用&#xff0c;对于申请SSL证书的域名&#xff0c;要求先验证这个域名的所有权。而目前可用的域名验证SSL证书方式有三种&#xff1a;分别是DNS验证、邮箱…

Kubernetes实战(二十七)-HPA实战

1 HPA简介 HPA 全称是 Horizontal Pod Autoscaler&#xff0c;用于POD 水平自动伸缩&#xff0c; HPA 可以 基于 POD CPU 利用率对 deployment 中的 pod 数量进行自动扩缩容&#xff08;除了 CPU 也可以基于自定义的指标进行自动扩缩容&#xff09;。pod 自动缩放不适用于无法…