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

目录

1. 前言

2. 虚拟DOM简介

 2.1什么是虚拟DOM?

 2.2为什么要有虚拟DOM?

3. Vue中的虚拟DOM

3.1 VNode类

3.2 VNode的类型

3.2.1 注释节点

3.2.2 文本节点

3.2.3 克隆节点

3.2.4 元素节点

3.2.5 组件节点

3.2.6 函数式组件节点

3.2.7 小结

3.3 VNode的作用

4. 总结

1. 前言

        虚拟DOM,这个名词作为当下的前端开发人员你一定不会陌生,至少会略有耳闻,但不会闻所未闻吧。这也是现在求职面试考察中非常高频的一个考点了。因为在当下的前端三大框架中关于虚拟DOM或多或少都有所涉及,那么接下来,我们就从源码角度出发,看看Vue中的虚拟DOM时怎样的。

2. 虚拟DOM简介

        由于本系列文章是针对Vue源码深入学习的,所以着重分析在Vue中对虚拟DOM是如何实现的,而对于虚拟DOM本身这个概念不做大篇幅的展开讨论,仅从以下几个问题简单介绍:

 2.1什么是虚拟DOM?

        所谓虚拟DOM,就是用一个JS对象来描述一个DOM节点,像如下示例:

<div class="a" id="b">我是内容</div>{tag:'div',        // 元素标签attrs:{           // 属性class:'a',id:'b'},text:'我是内容',  // 文本内容children:[]       // 子元素
}

我们把组成一个DOM节点的必要东西通过一个JS对象表示出来,那么这个JS对象就可以用来描述这个DOM节点,我们把这个JS对象就称为是这个真实DOM节点的虚拟DOM节点。

 2.2为什么要有虚拟DOM?

        我们知道,Vue是数据驱动视图的,数据发生变化视图就要随之更新,在更新视图的时候难免要操作DOM,而操作真实DOM又是非常耗费性能的,这是因为浏览器的标准就把 DOM 设计的非常复杂,所以一个真正的 DOM 元素是非常庞大的,如下所示:

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

上图中我们打印一个简单的空div标签,就打印出这么多东西,更不用说复杂的、深嵌套的DOM节点了。由此可见,直接操作真实DOM是非常消耗性能的。

        那么有没有什么解决方案呢?当然是有的。我们可以用JS的计算性能来换取操作DOM所消耗的性能。

        既然我们逃不掉操作DOM这道坎,但是我们可以尽可能少的操作DOM。那如何在更新视图的时候尽可能少的操作DOM呢?最直观的思路就是我们不要盲目的去更新视图,而是通过对比数据变化前后的状态,计算出视图中哪些地方需要更新,只更新需要更新的地方,而不需要更新的地方则不需关心,这样我们就可以尽可能少的操作DOM了。这也就是上面所说的用JS的计算性能来换取操作DOM的性能。

        我们可以用JS模拟出一个DOM节点,称之为虚拟DOM节点。当数据发生变化时,我们对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图。

        这就是虚拟DOM产生的原因以及最大的用途。

3. Vue中的虚拟DOM

       前文我们介绍了虚拟DOM的概念以及为什么要有虚拟DOM,那么在Vue中虚拟DOM是怎么实现的呢?接下来,我们从源码出发,深入学习一下。

3.1 VNode类

        我们说了,虚拟DOM就是用JS来描述一个真实的DOM节点。而在Vue中就存在了一个VNode类,通过这个类,我们就可以实例化出不同类型的虚拟DOM节点,源码如下:

// 源码位置:src/core/vdom/vnode.jsexport default class VNode {constructor (tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function) {this.tag = tag                                /*当前节点的标签名*/this.data = data        /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/this.children = children  /*当前节点的子节点,是一个数组*/this.text = text     /*当前节点的文本*/this.elm = elm       /*当前虚拟节点对应的真实dom节点*/this.ns = undefined            /*当前节点的名字空间*/this.context = context          /*当前组件节点对应的Vue实例*/this.fnContext = undefined       /*函数式组件对应的Vue实例*/this.fnOptions = undefinedthis.fnScopeId = undefinedthis.key = data && data.key           /*节点的key属性,被当作节点的标志,用以优化*/this.componentOptions = componentOptions   /*组件的option选项*/this.componentInstance = undefined       /*当前节点对应的组件的实例*/this.parent = undefined           /*当前节点的父节点*/this.raw = false         /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/this.isStatic = false         /*静态节点标志*/this.isRootInsert = true      /*是否作为跟节点插入*/this.isComment = false             /*是否为注释节点*/this.isCloned = false           /*是否为克隆节点*/this.isOnce = false                /*是否有v-once指令*/this.asyncFactory = asyncFactorythis.asyncMeta = undefinedthis.isAsyncPlaceholder = false}get child (): Component | void {return this.componentInstance}
}

        从上面的代码中可以看出:VNode类中包含了描述一个真实DOM节点所需要的一系列属性,如tag表示节点的标签名,text表示节点中包含的文本,children表示该节点包含的子节点等。通过属性之间不同的搭配,就可以描述出各种类型的真实DOM节点。

3.2 VNode的类型

        上一小节最后我们说了,通过属性之间不同的搭配,VNode类可以描述出各种类型的真实DOM节点。那么它都可以描述出哪些类型的节点呢?通过阅读源码,可以发现通过不同属性的搭配,可以描述出以下几种类型的节点。

  • 注释节点
  • 文本节点
  • 元素节点
  • 组件节点
  • 函数式组件节点
  • 克隆节点

接下来,我们就把这几种类型的节点描述方式从源码中一一对应起来。

3.2.1 注释节点

        注释节点描述起来相对就非常简单了,它只需两个属性就够了,源码如下:

// 创建注释节点
export const createEmptyVNode = (text: string = '') => {const node = new VNode()node.text = textnode.isComment = truereturn node
}

        从上面代码中可以看到,描述一个注释节点只需两个属性,分别是:textisComment。其中text属性表示具体的注释信息,isComment是一个标志,用来标识一个节点是否是注释节点。

3.2.2 文本节点

        文本节点描述起来比注释节点更简单,因为它只需要一个属性,那就是text属性,用来表示具体的文本信息。源码如下:

// 创建文本节点
export function createTextVNode (val: string | number) {return new VNode(undefined, undefined, undefined, String(val))
}

3.2.3 克隆节点

        克隆节点就是把一个已经存在的节点复制一份出来,它主要是为了做模板编译优化时使用,这个后面我们会说到。关于克隆节点的描述,源码如下:

// 创建克隆节点
export function cloneVNode (vnode: VNode): VNode {const cloned = new VNode(vnode.tag,vnode.data,vnode.children,vnode.text,vnode.elm,vnode.context,vnode.componentOptions,vnode.asyncFactory)cloned.ns = vnode.nscloned.isStatic = vnode.isStaticcloned.key = vnode.keycloned.isComment = vnode.isCommentcloned.fnContext = vnode.fnContextcloned.fnOptions = vnode.fnOptionscloned.fnScopeId = vnode.fnScopeIdcloned.asyncMeta = vnode.asyncMetacloned.isCloned = truereturn cloned
}

        从上面代码中可以看到,克隆节点就是把已有节点的属性全部复制到新节点中,而现有节点和新克隆得到的节点之间唯一的不同就是克隆得到的节点isClonedtrue

3.2.4 元素节点

        相比之下,元素节点更贴近于我们通常看到的真实DOM节点,它有描述节点标签名词的tag属性,描述节点属性如classattributes等的data属性,有描述包含的子节点信息的children属性等。由于元素节点所包含的情况相比而言比较复杂,源码中没有像前三种节点一样直接写死(当然也不可能写死),那就举个简单例子说明一下:

// 真实DOM节点
<div id='a'><span>难凉热血</span></div>// VNode节点
{tag:'div',data:{},children:[{tag:'span',text:'难凉热血'}]
}

        我们可以看到,真实DOM节点中:div标签里面包含了一个span标签,而span标签里面有一段文本。反应到VNode节点上就如上所示:tag表示标签名,data表示标签的属性id等,children表示子节点数组。

3.2.5 组件节点

组件节点除了有元素节点具有的属性之外,它还有两个特有的属性:

  • componentOptions :组件的option选项,如组件的props
  • componentInstance :当前组件节点对应的Vue实例
3.2.6 函数式组件节点

函数式组件节点相较于组件节点,它又有两个特有的属性:

  • fnContext:函数式组件对应的Vue实例
  • fnOptions: 组件的option选项
3.2.7 小结

        以上就是VNode可以描述的多种节点类型,它们本质上都是VNode类的实例,只是在实例化的时候传入的属性参数不同而已。

3.3 VNode的作用

说了这么多,那么VNodeVue的整个虚拟DOM过程起了什么作用呢?

        其实VNode的作用是相当大的。我们在视图渲染之前,把写好的template模板先编译成VNode并缓存下来,等到数据发生变化页面需要重新渲染的时候,我们把数据发生变化后生成的VNode与前一次缓存下来的VNode进行对比,找出差异,然后有差异的VNode对应的真实DOM节点就是需要重新渲染的节点,最后根据有差异的VNode创建出真实的DOM节点再插入到视图中,最终完成一次视图更新。

4. 总结

        本章首先介绍了虚拟DOM的一些基本概念和为什么要有虚拟DOM,其实说白了就是以JS的计算性能来换取操作真实DOM所消耗的性能。接着从源码角度我们知道了在Vue中是通过VNode类来实例化出不同类型的虚拟DOM节点,并且学习了不同类型节点生成的属性的不同,所谓不同类型的节点其本质还是一样的,都是VNode类的实例,只是在实例化时传入的属性参数不同罢了。最后探究了VNode的作用,有了数据变化前后的VNode,我们才能进行后续的DOM-Diff找出差异,最终做到只更新有差异的视图,从而达到尽可能少的操作真实DOM的目的,以节省性能。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安全的接口访问策略

渗透测试 一、Token与签名 一般客户端和服务端的设计过程中&#xff0c;大部分分为有状态和无状态接口。 一般用户登录状态下&#xff0c;判断用户是否有权限或者能否请求接口&#xff0c;都是根据用户登录成功后&#xff0c;服务端授予的token进行控制的。 但并不是说有了tok…

阿里云服务器多少钱一年?2024年阿里云服务器租用价格表

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

vivo发布2023 年度科技创新;阿里全新AI代理,可模拟人类操作手机

vivo 发布 2023 年度十大产品技术创新 近日&#xff0c;vivo 发布了「2023 年度科技创新」十大产品技术创新榜单&#xff0c;并将这些技术分为了 4 个板块。 「四大蓝科技」为 vivo 在去年推出的全新技术品牌&#xff0c;涵盖蓝晶芯片技术栈、蓝海续航系统、蓝心大模型、蓝河操…

SERVLET过滤器

SERVLET过滤器 全球因特网用户使用不同类型的Web浏览器访问应用服务器上存储的Web应用程序。每个浏览器根据对应的Web浏览器窗口中的设置显示应用程序中的信息。Web应用程序可能会有一些客户机的Web浏览器不支持的HTML标记或功能。这种情况下,应用程序在客户机的Web浏览器中可…

【人工智能】文本嵌入:向量存储与数据查询的智慧交织(12)

在当今信息激增的时代&#xff0c;将中文存储到向量数据库&#xff08;如Redis等&#xff09;并实现向量检索&#xff0c;正成为解决日常应用中文信息处理难题的关键利器。这项技术不仅赋予计算机对中文语义的理解能力&#xff0c;更让我们能够以更智能、高效的方式处理和检索中…

红队打靶练习:GLASGOW SMILE: 1.1

目录 信息收集 1、arp 2、nmap 3、nikto 4、whatweb 目录探测 1、gobuster 2、dirsearch WEB web信息收集 /how_to.txt /joomla CMS利用 1、爆破后台 2、登录 3、反弹shell 提权 系统信息收集 rob用户登录 abner用户 penguin用户 get root flag 信息收集…

深度解析 Netty 架构与原理

一共 28661字&#xff0c;耐心看完。 在阅读本文前最好有 Java 的 IO 编程经验&#xff08;知道 Java 的各种 IO 流&#xff09;&#xff0c;以及 Java 网络编程经验&#xff08;用 ServerSocket 和 Socket 写过 demo&#xff09;&#xff0c;并对 Java NIO 有基本的认识&…

深入探索 Express.js 的高级特性

引言 Express.js 是一个基于 Node.js 平台的 Web 开发框架&#xff0c;旨在提供一种简单、易于使用的方式来创建 Web 应用程序。由于其灵活性和可扩展性&#xff0c;它已经成为了 Node.js 社区最受欢迎的框架之一。在本文中&#xff0c;我们将重点介绍 Express.js 的高级特性&…

javascript原型

为了减少内存使用率可以使用原型对象 <script>//构造函数 公共的属性和方法 封装到Star 构造函数里面了//公共的属性写到 构造函数里面function Star(uname,age){this.unameunamethis.ageagethis.singfunction(){console.log(唱歌);}}const ldhnew Star(刘德华,18)const…

51单片机之数码管显示表白数字篇

朝菌不知晦朔 蟪蛄不知春秋 眼界决定境界 CSDN 请求进入专栏 是否进入《51单片机专栏》? 确定 目录 数码管的简介 数码管引脚定义 数码管的原理图 74HC245 代码实现 静态数码管的显示 动态数码管的显示 数码管实现表白画面 数码管的简介 L…

学习Android的第七天

目录 Android EditText 输入框 设置默认提示文本 范例 获得焦点后全选组件内所有文本内容 范例 限制EditText输入类型 android:inputType 值列表 范例 设置最小行&#xff0c;最多行&#xff0c;单行&#xff0c;多行&#xff0c;自动换行 范例 设置文字间隔 范例 …

通用的网站炫酷底部美化代码分享

网站炫酷底部美化代码介绍 这段代码采用了最新的前端技术&#xff0c;确保在各种浏览器和设备上都能完美展现。它包含响应式设计元素&#xff0c;这意味着无论用户是通过电脑、平板还是手机访问您的网站&#xff0c;底部都能呈现出最佳的效果。 此外&#xff0c;我们还特别注…