Vuejs设计与实现 — 渲染器核心:挂载与更新

前言

挂载更新渲染器 的核心功能,也是渲染器应该要提供的基本功能,而 挂载更新 又是基于 VNode 虚拟节点的,因为 VNode 节点描述了其对应的 真实 DOM 应该是什么样子的。

挂载与卸载

VNode 节点

无论是 vue 还是 react 都引入了 虚拟 DOM,只不过它们定义 虚拟 DOM 的结构不同,但本质上都只是一个普通的 JavaScript 对象。

VDOMVNode 是从 本质上 看是一个东西,因为 VDOMVNode 节点组成,每个 VNode 节点也能代表局部 VDOM,上篇文章中也提到过:VNodeVDOM 是可以互换的。

但从 整体上 看显然 VDOM 是包含或者等于 VNode,也就是说从严格意义上来讲,它们并不是一直相等的,取决于你的 VNode 节点的个数,如果它的节点数量是 1 那么它们是相等的。

不过现在所谈论的 VNode 就是 VDOM,谈论的 VDOM 就是 VNode,这只不过是一个简单的概念,不必过于纠结。

下面是 Vue3.x 中定义最基本的 VNode 结构:

  • vnode.type 是节点类型:标签、文本、注释、Fragment、Component 等
  • vnode.props 是节点属性数据:HTML Attributes 和 DOM Properties
const vnode = {__v_isVNode: true,__v_skip: true,type,props,key: props && normalizeKey(props),ref: props && normalizeRef(props),scopeId: currentScopeId,slotScopeIds: null,children,component: null,suspense: null,ssContent: null,ssFallback: null,dirs: null,transition: null,el: null,anchor: null,target: null,targetAnchor: null,staticCount: 0,shapeFlag,patchFlag,dynamicProps,dynamicChildren: null,appContext: null} as VNode

设置正确的元素属性

HTML Attributes 和 DOM Properties

  • HTML Attributes 指的就是定义在 HTML 标签上的属性,如:id="app"、type="text"、value="hello world" 等等

  • DOM Properties 指的是通过 JavaScript 来访问真实 DOM 元素时能够访问到的属性,很多 HTML Attributes 都能在 DOM Properties 上存在同名属性(如:el.id、el.title)等,不同名属性(如:el.className、el.textContext)等

  • 核心原则:HTML Attributes 的作用是设置 DOM Properties初始值

    dom.png

正确处理普通的 props

  • 通过 in 操作符判断 props.key 是否存在 el(即 DOM Properties)
    • 存在 则优先设置 DOM Properties,即 el[props.key] = props.value
    • 不存在 则通过 el.setAttribute(key, value) 完成属性设置
    • 针对 只读 属性的 DOM Properties,不能直接进行赋值,因此也必须转换为 el.setAttribute(key, value) 的处理,如:<input form="form1"> 中的 form 属性就是只读属性
      源码中抽离了 shouldSetAsProp 用于去判断是否可通过 DOM Properties 去更新:

在这里插入图片描述

特殊处理 class

Vue.jsclass 做了增强:

  • 指定 class 为普通 字符串
  • 指定 class 为一个 对象
  • 指定 class 为包含上述两种类型的 数组

由于 class 的值以多种形式存在,因此需要对 class 进行一些特殊处理,将 class 的值统一为字符串的形式,因为 HTML 只接收这样的 class

源码中通过 normaliz 处理不同的 class 类型,并统一返回字符串形式:

在这里插入图片描述

选择设置 class 最合适的方式

浏览器中设置 class 的方式有三种:el.className、el.classList、el.setAttribute,既然有多种方式,那么在选择时肯定要选择最优的设置方式,而其中最优的方式就是 el.className

可以做个小测试,时间不一定准确,但是差值却很明显:

const body = document.documentElement;console.time('className:')
for (let i = 0; i < 1000; i++) {body.className += i;
}
console.timeEnd('className:')console.time('setAttribute:')
for (let i = 0; i < 1000; i++) {body.setAttribute('class', body.className + ' ' + i); 
}
console.timeEnd('setAttribute:')console.time('classList:')
for (let i = 0; i < 1000; i++) {body.classList.add(i+''); 
}
console.timeEnd('classList:')// 输出结果:
className:: 5.760009765625 ms
setAttribute:: 651.76611328125 ms
classList:: 1750.427978515625 ms

事件处理

区分事件

在虚拟 DOM 中,事件可以被看作是一种特殊的属性,在 vue 中约定 vnode.props 对象中,凡是以字符串 on 开头的属性都视为 事件.

const vnode = {type: 'div',props: {onClick: () => {alert('hello');}},children: 'click here'
}

注册和更新事件

注册事件 通过 el.addEventListener 的方式进行注册即可,那如何实现 更新事件 呢?

最简单的方法:

  • 移除 之前的事件处理函数
  • 重新绑定 新的事件处理函数

但这种方式并不是最优的方式,毕竟需要来回 移除、注册 才能实现事件更新,有没有什么方法是可以只注册一次事件,也能实现事件更新的方式呢?

确实有,vue 中也是这么设计的:

  • 伪造一个事件处理函数 invoker.value,将真正的事件处理函数设置为 invoker.value 属性的值
  • 事件绑定时,先从 el._vei 读取对应的 invoker,若不存在,则将伪造的 invoker 作为事件处理函数,并将它缓存到 el._vei 属性中
  • 将真正的事件处理函数赋值给 invoker.value 属性,把伪造的 invoker 函数作为事件处理函数绑定到元素上
  • 事件触发时,实际上执行的是伪造的 invoker 函数,而 invoker 事件处理函数中会执行 invoker.value() 即 真正的事件处理函数
  • 事件需要进行更新时,直接将 invoker.value 的值重新赋值即可,不需通过 removeEventListener 移除事件
  • 当然若事件更新时确实属于事件移除操作,则还是需要通过 removeEventListener 移除事件

源码如下:

在这里插入图片描述

挂载节点

通过 patch(n1, n2, container, anchor = null, ...) 函数的初次调用实现元素挂载:

  • 首次调用 patch 函数时,n1 = null 因为是挂载阶段,因此没有旧 vnode,当 patch 函数执行时,会递归调用 mountElement 函数完成挂载
  • 第三个参数 anchor 是挂载点,最终通过 insertBefore 插入到文档中

在挂载过程中还会触发不同生命周期钩子的执行,具体的内容就不在详细进行分析了,感兴趣的可自行阅读源码

卸载操作

卸载操作实际上是发生在更新阶段,这里的更新时指,在初次挂载完成之后,后续渲染还会触发更新,只不过新 vnode 会变成 null,从而进入卸载阶段:

  • 容器的内容可能是一个或多个组件渲染的,当卸载发生时,应该正确地调用这些组件的 beforeUnmount、unmounted 等生命周期函数
  • 即使内容不是由组件渲染的,有的 元素上存在自定义指令 等,也应该要在卸载操作发生时,正确地执行对应的指令钩子函数
  • 同时需要移除绑定在 DOM 元素上的事件处理函数

基于以上原因,卸载不能简单的通过 innerHTML 来完成卸载操作,源码中通过 unmount 函数,以及一些对应移除函数实现卸载操作

在这里插入图片描述

更新子节点最佳方式

对于一个元素来说,其子节点拥有以下 3 种情况:

  • 没有子节点,即 vnode.children = null
  • 子节点是 文本节点,即 vnode.children 的值为字符串
  • 其他情况,无论是单个子元素,还是多个子节点(可能存在文本和元素的混合),都可以用数组来表示,即 vnode.children = [...]

有了规范化的子节点类型,那就可以总结更新子节点时的全部可能:

在这里插入图片描述

而在的实际的代码中,并不需要罗列去处理以上的所有情况,而更新方式必然也不是采用 “笨方式”卸载所有子节点,在挂载所有新节点,更好的做法是,通过 Diff 算法比较新旧两组子节点,试图最大程度复用 DOM 元素。

具体的 diff 算法,会在下一篇文章中进行介绍,并且会对比 vue2vue3 中的 diff 算法。

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

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

相关文章

k8s 综合项目笔记

综述 这篇笔记主要是为了记录下自己写 k8s 综合项目的过程。 由于自己之前已经写过简单的开发和运维项目&#xff0c;所以这里就结合一下&#xff0c;在搭建 k8s 集群后安装运维常用服务&#xff0c;比如 ansible 和 prometheus&#xff0c;用 NFS 实现数据存储同步&#xff0c…

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的&#xff1f;在文章开头我们提出这个疑问&#xff0c;带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容&#xff0c;在日常App 中非常常见&#xff0c;比如微博的发布与展示&#xff0c;朋友圈的发布与展示&#xff0c;都在使用富文…

LeetCode_231. 2 的幂_java

1、题目 231. 2 的幂https://leetcode.cn/problems/power-of-two/ 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n &#xff0c;则认为 n 是 2 的幂次方…

ComfyUI初体验

ComfyUI 我就不过多介绍了&#xff0c;安装和基础使用可以看下面大佬的视频&#xff0c;感觉自己靠图文描述的效果不一定好&#xff0c;大家看视频比较方便。 ComfyUI全球爆红&#xff0c;AI绘画进入“工作流时代”&#xff1f;做最好懂的Comfy UI入门教程&#xff1a;Stable D…

ArcGIS001:ArcGIS10.2安装教程

摘要&#xff1a;本文详细介绍arcgis10.2的安装、破解、汉化过程。 一、软件下载 安装包链接&#xff1a;https://pan.baidu.com/s/1T3UJ7t_ELZ73TH2wGOcfpg?pwd08zk 提取码&#xff1a;08zk 二、安装NET Framework 3.5 双击打开控制面板&#xff0c;点击【卸载程序】&…

dbt-codegen: dbt自动生成模板代码

dbt项目采用工程化思维&#xff0c;数据模型分层实现&#xff0c;支持描述模型文档和测试&#xff0c;非常适合大型数据工程项目。但也需要用户编写大量yaml描述文件&#xff0c;这个过程非常容易出错且无聊。主要表现&#xff1a; 手工为dbt模型编写yaml文件&#xff0c;这过…

STM32传感器模块编程实践(十一) ADC模数转换模块ADS1115简介及驱动源码

文章目录 一.概要二.ADS1115芯片介绍三.ADS1115芯片主要特性四.ADS1115模块接线说明五.ADS1115参考原理图六.通讯协议介绍七.STM32单片机与ADS1115模块实现电压采集实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 八.源代码工程下载九.小结 一.概要 ADC&#xff0c;全称为…

认识和使用 Vite 环境变量配置,优化定制化开发体验

Vite 官方中文文档&#xff1a;https://cn.vitejs.dev/ 环境变量 Vite 内置的环境变量如下&#xff1a; {"MODE": "development", // 应用的运行环境"BASE_URL": "/", // 部署应用时使用的 URL 前缀"PROD": false, //应用…

JavaScript完整笔记

JS引入 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。 通过 script 标签将 JavaScript 代码引入到 HTML 中&#xff0c;有两种方式&#xff1a; 内部方式 通过 script 标签包裹 JavaScript 代码 我们将 &…

使用FRP搭建内网穿透服务(新版toml配置文件,搭配反向代理方便内网网站访问)【使用frp搭建内网穿透】

FRP&#xff08;Fast Reverse Proxy&#xff09;是一个高性能的反向代理应用程序&#xff0c;主要用于内网穿透。它允许用户将内部网络服务暴露到外部网络&#xff0c;适用于 NAT 或防火墙环境下的服务访问。 他是一个开源的 服务 如果大家不想用 花生壳 软件&#xff0c;可以尝…

卷积神经网络评价指标

1.评价指标的作用 1. 性能评估&#xff1a;评价指标提供了一种量化的方式来衡量CNN模型的性能。通过这些指标&#xff0c;我们可以了解模型在特定任务上的表现&#xff0c;比如图像分类、目标检测或图像分割等。 2. 模型比较&#xff1a;不同的模型架构或训练策略可能会产生不…

基于SSM考研助手系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;教学秘书管理&#xff0c;考研资讯管理&#xff0c;考研名师管理&#xff0c;考研信息管理&#xff0c;系统管理 教学秘书账号功能包括&#xff1a;系统首页&#xff0c;个人中心…

如何快速解决游戏提示系统中的emp.dll缺失问题

emp.dll是一个动态链接库&#xff08;Dynamic Link Library, DLL&#xff09;文件&#xff0c;这类文件在Windows操作系统中扮演着至关重要的角色。它们包含了可由多个程序同时使用的代码和数据&#xff0c;其主要目的是实现模块化&#xff0c;以便于程序的更新和动态链接。emp…

es实现自动补全

目录 自动补全 拼音分词器 安装拼音分词器 第一步&#xff1a;下载zip包&#xff0c;并解压缩 第二步&#xff1a;去docker找到es-plugins数据卷挂载的位置&#xff0c;并进入这个目录 第三步&#xff1a;把拼音分词器的安装包拖到这个目录下 第四步&#xff1a;重启es 第…

RV1126音视频学习(二)-----VI模块

文章目录 前言2.RV1126的视频输入vi模块2.1什么是VI模块2.3RV1126VI模块主要APIRK_MPI_SYS_Init()RK_MPI_VI_SetChnAttrRK_MPI_VI_EnableChnRK_S32 RK_MPI_VI_DisableChnRK_MPI_VI_StartStreamRK_MPI_SYS_GetMediaBufferRK_MPI_MB_GetPtrRK_MPI_MB_GetSizeRK_MPI_MB_ReleaseBuf…

【NOIP提高组】加分二叉树

【NOIP提高组】加分二叉树 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 设一个n个节点的二叉树tree的中序遍历为&#xff08;l,2,3,…,n&#xff09;&#xff0c;其中数字1,2,3,…,n为节点编号。每个节点都有一个分数&#xff08;均为正整…

读《认知觉醒》:浅谈费曼技巧

最近在阅读《认知觉醒》这本书&#xff0c;封面如下&#xff1a; 读到了里面对于费曼技巧的介绍&#xff08;在第八章&#xff09;&#xff0c;感觉受到了一些启发&#xff0c;在这里分享给大家。 其实之前很早就接触过了费曼技巧&#xff0c;但是并没有很好的应用起来&#x…

零代码快速开发智能体 |甘肃旅游通

零代码快速开发智能体 &#xff5c;甘肃旅游通 本文仅用于文心智能体的活动征文 参与人&#xff1a;mengbei_admin 文心智能体平台是人工智能领域的佼佼者。它拥有强大的语言理解与生成能力&#xff0c;能精准回应各种问题&#xff0c;出色完成文本创作、知识问答和翻译等任…

线性表之双向链表

链表花里胡哨&#xff0c;一应俱全 前言 在这之前&#xff0c;我们已经学习了单链表。我们发现这些链表都是一个接一个朝一个方向接下去&#xff0c;有时&#xff0c;我们想要查找某个结点的时候还得从头开始遍历查找&#xff0c;尽管我们已经学习了顺序表&#xff0c;查找某个…

免费PDF页面提取小工具

下载地址 https://download.csdn.net/download/woshichenpi/89922797 使用说明&#xff1a;PDF页面提取工具 1. 启动应用程序 双击程序的启动图标或者通过命令行运行程序。 2. 选择PDF文件 在应用程序窗口中找到“选择PDF”按钮并点击它。在弹出的文件选择对话框中&#x…