【Vue2.0源码学习】虚拟DOM篇-Vue中的虚拟DOM

news2024/12/25 1:23:24

文章目录

    • 1. 前言
    • 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本身这个概念不做大篇幅的展开讨论,仅从以下几个问题简单介绍:

  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. 为什么要有虚拟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 节点数据会占据更大的内存,当我们频繁的去做 DOM 更新,会产生一定的性能问题,因为 DOM 的更新有可能带来页面的重绘或重排。

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

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

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

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

另外,使用虚拟 DOM 也能使得 Vue 不再依赖于浏览器环境。我们可以很容易的在 Broswer 端或者服务器端操作虚拟 DOM, 需要 render 时再将虚拟 DOM 转换为真实 DOM 即可。这也使得 Vue 有了实现服务器端渲染的能力。

3. Vue中的虚拟DOM

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

3.1 VNode类

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

// 源码位置:src/core/vdom/vnode.js

export 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 = undefined
    this.fnScopeId = undefined
    this.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 = asyncFactory
    this.asyncMeta = undefined
    this.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 = text
  node.isComment = true
  return 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.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isComment = vnode.isComment
  cloned.fnContext = vnode.fnContext
  cloned.fnOptions = vnode.fnOptions
  cloned.fnScopeId = vnode.fnScopeId
  cloned.asyncMeta = vnode.asyncMeta
  cloned.isCloned = true
  return 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.coloradmin.cn/o/526985.html

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

相关文章

Android Studio Flamingo (火烈鸟) 升级踩坑记录

由于想要验证Compose最新的debug特性&#xff0c;而我目前使用的版本&#xff08;Dolphin 小海豚&#xff09;不支持&#xff0c;查看官网说明需要最新版本&#xff0c;所以不得已进行了一下Android Studio版本升级&#xff0c;过程中遇到一些问题&#xff0c;本文仅做记录。&a…

FPGA驱动WM8731音频收发全网最细讲解 提供2套工程源码和技术支持

目录 1、前言2、WM8731音频解读3、vivado工程1介绍4、vivado工程2介绍5、上板调试验证6、资料获取 1、前言 本文用FPGA驱动WM8731音频芯片&#xff0c;实现音频相关的应用&#xff0c;适用于在校学生做毕业设计、研究生项目开发&#xff0c;也适用于在职工程师做项目开发&…

Ae 首选项:启动和修复

使用“首选项”里面的启动和修复 Start and Repair面板&#xff0c;可以解决 Ae 中的首选项设置问题&#xff0c;而无需更改、删除或者重新进行设置。 启动选项 Startup Options 启用主屏幕 Enable Home Screen 确定是否在启动 Ae 时显示主屏幕。 更改将在下次启动时有效。 显示…

element-plus 问题

对话框内部下拉框会在左上角 在对话框内打开下拉框后点击关闭按钮&#xff0c;尚未关闭的下拉框会在左上角出现&#xff08;或闪现&#xff09; 解决方案&#xff1a; popper-append-to-body 此方法失效&#xff0c;改用 :teleported"false" teleported&#xff1a;…

JDBC和数据库连接池

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/java-tutorial 】或者公众号【AIShareLab】回复 java 也可获取。 文章目录 JDBC 概述基本介绍模拟JDBCJDBC 带来的好处 JDBC 快速入门JDBC 程序编写步骤JDBC 第一个程序 获取数据库连接5种方式方式1…

接入 GPT-4 的 AI 虚拟女友,一周狂挣 50 万!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 随着 ChatGPT 的爆火&#xff0c;互联网上也衍生出不少令人直呼脑洞大开的 AI 应用。 最近&#xff0c;国外一位名为 Caryn Marjorie 的小姐姐搞了个骚操作&#xff0c;基于 GPT-4 调教出来…

Spring Cloud LoadBalancer是什么?

什么是Spring Cloud LoadBalancer Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器, 用来替代 Ribbon。 Spring官方提供了两种负载均衡的客户端&#xff1a; RestTemplate RestTemplate是Spring提供的用于访问Rest服务的客户端&#xff0c;RestTempla…

30从零开始学Java之详解面向对象的7种创建方式

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家介绍了Java中的类及其特点、创建过程等内容&#xff0c;相信你…

【Nginx高级篇】Lua基础语法和OpenResty的安装

目录 一、前言 二、Lua基础语法 hello world 保留关键字 注释 变量 字符串 空值 布尔类型 作用域 控制语句 if-else for循环 函数 赋值 返回值 Table 数组 遍历 成员函数 三、openresty的安装 &#xff08;一&#xff09;预编译安装 &#xff08;二&…

VMware中Ubuntu拓展磁盘容量的两种方式 图形化方式命令行磁盘分区方式(亲测有效简单且详细)linux磁盘分区

文章目录 前言1. 软件设置扩容2. 容量查看3. 容量拓展实现3.1 图形化容量拓展分配3.2 磁盘命令行方式拓展容量3.2.1 确定磁盘挂载目录3.2.2 系统磁盘扩容 4. 分盘扩容TIPS&#xff1a;总结&#xff1a; 前言 在用VMware虚拟机的情况下&#xff0c;一开始分配的容量在使用过程中…

PMP项目管理-[第十二章]采购管理

采购管理知识体系&#xff1a; 规划采购管理&#xff1a; 实施采购&#xff1a; 控制采购&#xff1a; 12.1 规划采购管理 定义&#xff1a;记录项目采购决策、明确采购方法、识别潜在卖方的过程 作用&#xff1a;确定是否从项目外部获取货物或服务.如果是&#xff0c;则还要确…

循环结构程序设计

一、循环结构语句 C语言提供了三种循环语句&#xff08;for语句&#xff09;、while语句和do-while语句。 for语句&#xff1a; for(表达式1 &#xff1b; 表达式2 &#xff1b; 表达式3) {   循环体语句&#xff1b; } for语句的执行过程&#xff1a; 首先计算表达式1。判断…

【LED子系统】五、核心层详解(二)

个人主页&#xff1a;董哥聊技术 我是董哥&#xff0c;高级嵌入式软件开发工程师&#xff0c;从事嵌入式Linux驱动开发和系统开发&#xff0c;曾就职于世界500强公司&#xff01; 创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01; 文章目录…

基于B/S架构、可替代付费商业软件的一站式量化交易平台

产品简介 这是一个面向程序员的量化交易软件&#xff0c;用于期货、股票、外汇、炒币等多种交易场景&#xff0c;实现自动交易。已对接了CTP接口&#xff08;国内期货&#xff09;、老虎证券接口&#xff08;美股港股&#xff09;。 功能特性&#xff1a; 一站式平台&#x…

Protell99SE祭文

Protell99SE祭文 大概是在21年前的今天&#xff0c;我和你结合在一起&#xff0c;陪伴走过无数的设计。 我的感觉&#xff0c;大概是在2021年吧&#xff0c;你逐渐离我远去。啊&#xff0c;Protel99SE时代一去不复返了。 我用了你21年&#xff0c;虽着AD软件的到来&#xff…

【C++】19.C++11

1.C11 auto 范围for 新容器 线程库列表初始化右值引用和移动语义 lambda表达式容器支持花括号列表初始化 本质是增加一个initializer_list的构造函数initializer_list支持花括号 2.列表初始化 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <ve…

​数字化转型升级之工业元宇宙与AIGC

月説小飞象交流会 生活就是&#xff0c;面对复杂&#xff0c;保持欢喜。心烦时&#xff0c;记住三句话&#xff1a;1、算了吧。2、没关系。3、会过去的。 内部交流│24期 数字化转型升级 工业元宇宙与AIGC data analysis ●●●● 分享人&#xff1a;李铁军 ‍ 现如今数字化不再…

定风波、渡重山、至未来:2023中国数字能源生态大会开启的新旅程

全球碳中和的时代背景下&#xff0c;面向3060发展目标&#xff0c;新使命、新技术、新应用的到来&#xff0c;都给能源产业带来了持续变革的必要性与可能性。 2023年5月11日&#xff0c;在2023中国数字能源生态大会上&#xff0c;华为数字能源技术有限公司总裁侯金龙发表了“融…

【微电网】含风、光、储联合发电的微电网优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

2023 年 IntelliJ IDEA 下载安装教程,超详细图文教程,亲测可用

. IDEA 下载 1、打开浏览器输入https://www.jetbrains.com/&#xff0c;进入 Jetbrains官网&#xff0c;点击 Developer Tools&#xff0c;再点击 Intellij IDEA 2、点击中间的 Download&#xff0c;进入IDEA下载界面 3、选择左边的 Ultimate 版本进行下载安装。Ultimate 版…