vue3-渲染机制

news2025/1/20 2:00:39

渲染机制

Vue 是如何将一份模板转换为真实的 DOM 节点的,又是如何高效地更新这些节点的呢?我们接下来就将尝试通过深入研究 Vue 的内部渲染机制来解释这些问题。

虚拟 DOM

你可能已经听说过“虚拟 DOM”的概念了,Vue 的渲染系统正是基于这个概念构建的。

虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步。这个概念是由 React 率先开拓,随后被许多不同的框架采用,当然也包括 Vue。

与其说虚拟 DOM 是一种具体的技术,不如说是一种模式,所以并没有一个标准的实现。我们可以用一个简单的例子来说明:

const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

这里所说的 vnode 即一个纯 JavaScript 的对象 (一个“虚拟节点”),它代表着一个 <div> 元素。它包含我们创建实际元素所需的所有信息。它还包含更多的子节点,这使它成为虚拟 DOM 树的根节点。

一个运行时渲染器将会遍历整个虚拟 DOM 树,并据此构建真实的 DOM 树。这个过程被称为挂载 (mount)。

如果我们有两份虚拟 DOM 树,渲染器将会有比较地遍历它们,找出它们之间的区别,并应用这其中的变化到真实的 DOM 上。这个过程被称为更新 (patch),又被称为“比对”(diffing) 或“协调”(reconciliation)。

虚拟 DOM 带来的主要收益是它让开发者能够灵活、声明式地创建、检查和组合所需 UI 的结构,同时只需把具体的 DOM 操作留给渲染器去处理。

渲染管线

从高层面的视角看,Vue 组件挂载时会发生如下几件事:

  • 编译:Vue 模板被编译为渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。

  • 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为响应式副作用执行,因此它会追踪其中所用到的所有响应式依赖。

  • 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。

模板 vs. 渲染函数

Vue 模板会被预编译成虚拟 DOM 渲染函数。Vue 也提供了 API 使我们可以不使用模板编译,直接手写渲染函数。在处理高度动态的逻辑时,渲染函数相比于模板更加灵活,因为你可以完全地使用 JavaScript 来构造你想要的 vnode。

那么为什么 Vue 默认推荐使用模板呢?有以下几点原因:

模板更贴近实际的 HTML。这使得我们能够更方便地重用一些已有的 HTML 代码片段,能够带来更好的可访问性体验、能更方便地使用 CSS 应用样式,并且更容易使设计师理解和修改。

由于其确定的语法,更容易对模板做静态分析。这使得 Vue 的模板编译器能够应用许多编译时优化来提升虚拟 DOM 的性能表现 (下面我们将展开讨论)。

在实践中,模板对大多数的应用场景都是够用且高效的。渲染函数一般只会在需要处理高度动态渲染逻辑的可重用组件中使用。想了解渲染函数的更多使用细节可以去到渲染函数 & JSX 章节继续阅读。

带编译时信息的虚拟 DOM

虚拟 DOM 在 React 和大多数其他实现中都是纯运行时的:更新算法无法预知新的虚拟 DOM 树会是怎样,因此它总是需要遍历整棵树、比较每个 vnode 上 props 的区别来确保正确性。另外,即使一棵树的某个部分从未改变,还是会在每次重渲染时创建新的 vnode,带来了大量不必要的内存压力。这也是虚拟 DOM 最受诟病的地方之一:这种有点暴力的更新过程通过牺牲效率来换取声明式的写法和最终的正确性。

但实际上我们并不需要这样。在 Vue 中,框架同时控制着编译器和运行时。这使得我们可以为紧密耦合的模板渲染器应用许多编译时优化。编译器可以静态分析模板并在生成的代码中留下标记,使得运行时尽可能地走捷径。与此同时,我们仍旧保留了边界情况时用户想要使用底层渲染函数的能力。我们称这种混合解决方案为带编译时信息的虚拟 DOM。

下面,我们将讨论一些 Vue 编译器用来提高虚拟 DOM 运行时性能的主要优化:

静态提升

在模板中常常有部分内容是不带任何动态绑定的:

<div>
  <div>foo</div> <!-- 需提升 -->
  <div>bar</div> <!-- 需提升 -->
  <div>{{ dynamic }}</div>
</div>

foo 和 bar 这两个 div 是完全静态的,没有必要在重新渲染时再次创建和比对它们。Vue 编译器自动地会提升这部分 vnode 创建函数到这个模板的渲染函数之外,并在每次渲染时都使用这份相同的 vnode,渲染器知道新旧 vnode 在这部分是完全相同的,所以会完全跳过对它们的差异比对。

此外,当有足够多连续的静态元素时,它们还会再被压缩为一个“静态 vnode”,其中包含的是这些节点相应的纯 HTML 字符串。(示例)。这些静态节点会直接通过 innerHTML 来挂载。同时还会在初次挂载后缓存相应的 DOM 节点。如果这部分内容在应用中其他地方被重用,那么将会使用原生的 cloneNode() 方法来克隆新的 DOM 节点,这会非常高效。

更新类型标记

对于单个有动态绑定的元素来说,我们可以在编译时推断出大量信息:

<!-- 仅含 class 绑定 -->
<div :class="{ active }"></div>

<!-- 仅含 id 和 value 绑定 -->
<input :id="id" :value="value">

<!-- 仅含文本子节点 -->
<div>{{ dynamic }}</div>

在为这些元素生成渲染函数时,Vue 在 vnode 创建调用中直接编码了每个元素所需的更新类型:

createElementVNode("div", {
  class: _normalizeClass({ active: _ctx.active })
}, null, 2 /* CLASS */)

最后这个参数 2 就是一个更新类型标记 (patch flag)。一个元素可以有多个更新类型标记,会被合并成一个数字。运行时渲染器也将会使用位运算来检查这些标记,确定相应的更新操作:

if (vnode.patchFlag & PatchFlags.CLASS /* 2 */) {
  // 更新节点的 CSS class
}

位运算检查是非常快的。通过这样的更新类型标记,Vue 能够在更新带有动态绑定的元素时做最少的操作。

Vue 也为 vnode 的子节点标记了类型。举例来说,包含多个根节点的模板被表示为一个片段 (fragment),大多数情况下,我们可以确定其顺序是永远不变的,所以这部分信息就可以提供给运行时作为一个更新类型标记。

export function render() {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    /* children */
  ], 64 /* STABLE_FRAGMENT */))
}

运行时会完全跳过对这个根片段中子元素顺序的重新协调过程。

树结构打平

再来看看上面这个例子中生成的代码,你会发现所返回的虚拟 DOM 树是经一个特殊的 createElementBlock() 调用创建的:

export function render() {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    /* children */
  ], 64 /* STABLE_FRAGMENT */))
}

这里我们引入一个概念“区块”,内部结构是稳定的一个部分可被称之为一个区块。在这个用例中,整个模板只有一个区块,因为这里没有用到任何结构性指令 (比如 v-if 或者 v-for)。

每一个块都会追踪其所有带更新类型标记的后代节点 (不只是直接子节点),举例来说:

<div> <!-- root block -->
  <div>...</div>         <!-- 不会追踪 -->
  <div :id="id"></div>   <!-- 要追踪 -->
  <div>                  <!-- 不会追踪 -->
    <div>{{ bar }}</div> <!-- 要追踪 -->
  </div>
</div>

编译的结果会被打平为一个数组,仅包含所有动态的后代节点:

div (block root)
- div 带有 :id 绑定
- div 带有 {{ bar }} 绑定

当这个组件需要重渲染时,只需要遍历这个打平的树而非整棵树。这也就是我们所说的树结构打平,这大大减少了我们在虚拟 DOM 协调时需要遍历的节点数量。模板中任何的静态部分都会被高效地略过。

v-if 和 v-for 指令会创建新的区块节点:

<div> <!-- 根区块 -->
  <div>
    <div v-if> <!-- if 区块 -->
      ...
    <div>
  </div>
</div>

一个子区块会在父区块的动态子节点数组中被追踪,这为他们的父区块保留了一个稳定的结构。

对 SSR 激活的影响

更新类型标记和树结构打平都大大提升了 Vue SSR 激活的性能表现:

  • 单个元素的激活可以基于相应 vnode 的更新类型标记走更快的捷径。

  • 在激活时只有区块节点和其动态子节点需要被遍历,这在模板层面上实现更高效的部分激活。

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

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

相关文章

基于JAVA(springboot)后台微信自我健康管理小程序系统设计与实现

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

Javaweb基础-前端工程化学习笔记

前端工程化&#xff1a; 一.ES6 变量与模版字符串 let 和var的差别&#xff1a; <script>//1. let只有在当前代码块有效代码块. 代码块、函数、全局{let a 1var b 2} console.log(a); // a is not defined 花括号外面无法访问console.log(b); // 可以正常输出…

DTAN: Diffusion-based Text Attention Network for medical imagesegmentation

DTAN:基于扩散的医学图像分割文本关注网络 摘要 在当今时代&#xff0c;扩散模型已经成为医学图像分割领域的一股开创性力量。在此背景下&#xff0c;我们引入了弥散文本注意网络(Diffusion text - attention Network, DTAN)&#xff0c;这是一个开创性的分割框架&#xff0c…

【c++ debug】记一次protobuf结构相关的coredump问题

文章目录 1. 问题现象2. 问题描述3. 问题分析4. 问题根因5. 问题修复6. 补充&#xff1a;类成员变量定义为引用类型 1. 问题现象 其中curr_lanes是一个目标上一帧的当前车道current_lanes_curr_lane是lane_id对应的LaneInfo信息现象&#xff1a;在lane_info->lane().success…

《隐私计算简易速速上手小册》第5章:隐私计算在不同行业的应用(2024 最新版)

文章目录 5.1 金融行业5.1.1 基础知识5.1.2 重点案例:欺诈检测系统5.1.3 拓展案例 1:信用评分模型5.1.4 拓展案例 2:市场趋势分析5.2 医疗保健行业5.2.1 基础知识5.2.2 重点案例:匿名化患者数据分析5.2.3 拓展案例 1:使用差分隐私进行临床试验数据分析5.2.4 拓展案例 2:安…

【机构vip教程】​python(1):python正则表达式匹配指定的字符开头和指定的字符结束

一&#xff0c;使用python的re.findall函数&#xff0c;匹配指定的字符开头和指定的字符结束 代码示例&#xff1a; 1 import re 2 # re.findall函数;匹配指定的字符串开头和指定的字符串结尾(前后不包含指定的字符串) 3 str01 hello word 4 str02 re.findall((?<e).*?…

大模型LLM训练显存消耗详解

参考论文&#xff1a;ZeRO: Memory Optimizations Toward Training Trillion Parameter Models 大模型的显存消耗一直都是面试常见的问题&#xff0c;这次我就彻彻底底的根据论文ZeRO中的调研和分析做一次分析 显存消耗的两个部分&#xff1a;Model States&#xff08;跟模型的…

【机构vip教程】Android SDK手机测试环境搭建

Android SDK 的安装和环境变量的配置 前置条件&#xff1a;需已安装 jdk1.8及 以上版本 1、下载Android SDK&#xff0c;解压后即可&#xff08;全英文路径&#xff09;&#xff1b;下载地址&#xff1a;http://tools.android-studio.org/index.php/sdk 2、新建一个环境变量&…

linux内核模块module_put()函数详解--03

对应module_put()函数详细用法分享。 第一&#xff1a;函数简介 //函数原型 void module_put(struct module * module) //函数功能 该函数功能是将一个特定模块module的引用计数减一 这样当一个模块的引用计数不为0而不能被内核卸载的 时候&#xff0c;可以调用该函数一次或多…

这样用TVS管

对于工程师来说&#xff0c;浪涌保护不仅仅是选择合适的电源板或者拔下几根电缆&#xff0c;主要涉及在 PCB 布局中放置瞬态保护组件并应用明确的接地策略。 TVS 二极管是用于保护PCB布局中组件的常用组件&#xff0c;这些组件放置在数据线上&#xff0c;一旦电路中接收到ESD脉…

出生医学证明档案管理系统

出生医学证明档案管理系统是一种用于管理和维护出生医学证明档案的软件系统。该系统可以帮助医院、出生登记机构和其他相关部门有效地管理和存储出生医学证明档案&#xff0c;提高工作效率和数据安全性。 专久智能出生医学证明档案管理系统的核心功能包括&#xff1a; 1. 档案管…

Linux超详细笔记

文章目录 Linux学习笔记操作系统Linux初识Linux的诞生Linux内核Linux发行版 虚拟机VMware安装远程连接Linux系统FinalShellFinalShell连接Linux WSL配置UbuntuLinux常用命令1.入门2.ls命令cd命令3.pwd命令4.相对路径和绝对路径5.mkdir命令6.文件操作命令&#xff08;1&#xff…

【机构vip教程】Charles(1):Charles的介绍及安装

Charles Charles 是在 Mac &#xff08;Charles是跨平台的 &#xff09;下常用的网络封包截取工具&#xff0c;在做移动开发、测试时&#xff0c;我们为了调试与服务器端的网络通讯协议&#xff0c;常常需要截取网络封包来分析。Charles是一个HTTP代理服务器,HTTP监视器,反转代…

从零开始的 dbt 入门教程 (dbt core 开发进阶篇)

引 在上一篇文章中&#xff0c;我们花了专门的篇幅介绍了 dbt 更多实用的命令&#xff0c;那么我们继续按照之前的约定来聊 dbt 中你可能会遇到的疑惑以及有用的概念&#xff0c;如果你是 dbt 初学者&#xff0c;我相信如下知识点一定会对你有极大的帮助&#xff1a; 了解 db…

【Linux篇】Linux项目自动化构建工具-make/Makefile

&#x1f49b;不要有太大压力&#x1f9e1; &#x1f49b;生活不是选择而是热爱&#x1f9e1; &#x1f49a;文章目录&#x1f49a; 什么是make/Makefilemakefile认识makefilemakefile的编写伪目标 Linux下多程序编译 什么是make/Makefile 在实际工作中&#xff0c;一个项目可能…

在职阿里6年,一个28岁女软件测试工程师的心声

简单的先说一下&#xff0c;坐标杭州&#xff0c;16届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; 其中成功的有4家&am…

芯片的分类

目录 通用处理器数字信号处理器专用处理器 通用处理器 我们常听说的中央处理器CPU就是一种典型的通用处理器&#xff08;GPP&#xff09;。这种处理器多使用片上系统&#xff08;SoC&#xff09;的设计理念&#xff0c;在处理器上集成各种功能模块&#xff0c;每一种功能都是用…

Python爬虫详解(一看就懂)

爬虫 爬虫是什么 爬虫简单的来说就是用程序获取网络上数据这个过程的一种名称。 爬虫的原理 如果要获取网络上数据&#xff0c;我们要给爬虫一个网址&#xff08;程序中通常叫URL&#xff09;&#xff0c;爬虫发送一个HTTP请求给目标网页的服务器&#xff0c;服务器返回数据…

thinkphp+vue企业产品展示网站f7enu

本文首先介绍了企业产品展示网站管理技术的发展背景与发展现状&#xff0c;然后遵循软件常规开发流程&#xff0c;首先针对系统选取适用的语言和开发平台&#xff0c;根据需求分析制定模块并设计数据库结构&#xff0c;再根据系统总体功能模块的设计绘制系统的功能模块图&#…

三防平板电脑丨亿道工业三防平板丨三防平板定制丨机场维修应用

随着全球航空交通的增长和机场运营的扩展&#xff0c;机场维护的重要性日益凸显。为确保机场设施的安全和顺畅运行&#xff0c;采取适当的措施来加强机场维护至关重要。其中&#xff0c;三防平板是一种有效的工具&#xff0c;它可以提供持久耐用的表面保护&#xff0c;使机场维…