当前位置: 首页 > news >正文

mini-vue 的设计

mini-vue 的设计

mini-vue 使用流程与结果预览:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"></div><button class="btn">执行patch</button><hr></hr><button class="refreshBtn">刷新页面</button></body><script src="./renderer.js"></script><script src="./mount.js"></script><script src="./patch.js"></script><script>/*** 思路:* 1. 通过 h 函数创建 vnode* 2. 通过 mount 函数挂载*/// 1. 生成 vnodeconst vnode = h("div",{ class: "youxiaobei", id: "oldId", del: "这是将被删除的" },[h("ul", null, [h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),]),h("button", null, "这是将被保留的小小按钮"),h("h4", null, "以上内容都会被比较为不同然后删除,包括我")]);// 2. 挂载,生成真实 dom,添加到 container 容器中const container = document.getElementById("app");mount(vnode, container);// 3. 新节点 01 (最外层 tagName 不一样,直接都被替换了)const newVnode = h("h2", { class: "newNode", id: "newId" }, [h("button", null, "我是你后来加的小按钮"),]);const btn = document.querySelector('.btn')btn.addEventListener("click", () => {patch(vnode, newVnode);btn.disabled = true;},true);const refreshBtn = document.querySelector('.refreshBtn')refreshBtn.addEventListener("click", () => {location.reload()})</script><style>/* 新的节点背景色是红色的 */.newNode {background-color: red; }</style>
</html>

执行 patch 前:

img01

执行后:

img02

1. h 函数

h 函数也就是 render 函数,作用简单:返回一个 Vnode 虚拟节点,但很重要!

/*** h 函数* 功能:返回vnode** @param {String} tagName  - 标签名* @param {Object | Null} props  - 传递过来的参数* @param {Array | String} children  - 子节点* @return {vnode} 虚拟节点*/
const h = (tagName, props, children) => {// 直接返回一个对象,里面包含vnode结构return {tagName,props,children,};
};

2. 响应式

考虑以下功能:

  1. 收集依赖某个数据的函数
  2. 当数据变化后,重新执行依赖此数据的函数
/*** 实现一个类* 构造一个 订阅列表 subscribers set 对象* 收集者 addEffect  添加影响的函数,往 subscribers 里面添加* 通知者 notifier 通知函数, 依次执行 subscribers 里面的函数*/
class Dep {constructor() {// 1.订阅列表 构造一个 subscribers set 对象this.subscribers = new Set();}// 2. 收集者 addEffect 添加影响的函数addEffect(effect) {this.subscribers.add(effect);}// 3. 通知者 notifiernotifier() {this.subscribers.forEach((effect) => {effect();});}
}const dep = new Dep();let count = 1;const addFun = function () {console.log(++count);
};addFun(); // 2// 收集订阅
dep.addEffect(addFun);// 当数据改变后
count = 100;// 通知者通知函数重执行
dep.notifier(); // 101

3. mount 函数

挂载 Vnode 为真实的 DOM 元素

/*** mount 函数* 功能:挂载 vnode 为 真实dom* 重点:递归调用处理子节点** @param {Object} vnode -虚拟节点* @param {elememt} container -需要被挂载节点*/
const mount = (vnode, container) => {// 1. 创建出真实元素, 给 vnode 添加 el 属性const el = (vnode.el = document.createElement(vnode.tagName));// 2. 处理 propsif (vnode.props) {for (const key in vnode.props) {const value = vnode.props[key];// 2.1 prop 是函数if (key.startsWith("on")) {el.addEventListener(key.slice(2).toLowerCase(), value);} else {// 2.2 prop 是字符串el.setAttribute(key, value);}}}// 3. 处理 childrenif (vnode.children) {// 3.1 如果 children 是字符串,直接设置文本内容if (typeof vnode.children === "string") {el.textContent = vnode.children;}// 3.2 如果 children 是数组,递归挂载每个子节点else {// 先拿到里面的每一个 vnodevnode.children.forEach((item) => {// 再把里面的vnode递归调用mount(item, el);});}}// 4. 挂载container.appendChild(el);
};

04. patch 函数

patch 对比节点数组,优化性能

/*** 节点比较* 调用时机:节点发生变化(数量,内容)* 功能:比较节点数组,尽可能减少 DOM 操作*//*** @param {Vnode} n1 - 旧节点* @param {Vnode} n2 - 新节点*/
const patch = (n1, n2) => {// 节点不相同,卸载旧节点,挂载新节点if (n1.tagName !== n2.tagName) {const parentElementNode = n1.el.parentElement;parentElementNode.removeChild(n1.el);mount(n2, parentElementNode);} else {// 1. 取出 element 并保存到 n2const el = (n2.el = n1.el);// 2. 处理 propsconst oldProps = n1.props || {};const newProps = n2.props || {};for (const key in newProps) {const oldValue = oldProps[key];const newValue = newProps[key];// 2.1 值不同才替换if (oldValue !== newValue) {if (key.startsWith("on")) {el.addEventListener(key.slice(2).toLowerCase(), newValue);} else {// 2.2 prop 是字符串el.setAttribute(key, newValue);}}}// 3. 删除旧的 propsfor (const key in oldProps) {// 如果旧 key 不在新的 props 里if (!(key in newProps)) {const oldValue = oldProps[key];if (key.startsWith("on")) {el.removeEventListener(key.slice(2).toLowerCase(), oldValue);} else {// 2.2 prop 是字符串el.removeAttribute(key, oldValue);}}}// 4. 处理 childrenconst oldChildren = n1.children;const newChildren = n2.children;// children 字符串if (typeof newChildren === "string") {// 4.1 如果新 children 是字符串,直接设置文本内容if (oldChildren !== newChildren) {el.textContent = newChildren;} else {el.innerHTML = newChildren;}} else {// 4.2 如果新 children 是数组,递归挂载每个子节点// 如果旧 children 的是字符串if (typeof oldChildren === "string") {el.innerHTML = "";// 遍历 childrennewChildren.forEach((item) => {mount(item, el);});} else {// 两个都是数组,开始 diff 算法// n1: [a,b,d]// n2: [b,a,c,f]/*** 没有 key*/if (!n1.props.key && !n2.props.key) {// 4.3.1 获取两个 vnode 数组的公共长度,比较相同的const commonLength = Math.min(oldChildren.length, newChildren.length);for (let i = 0; i < commonLength; i++) {patch(oldChildren[i], newChildren[i]);}// 4.3.2 新的长度多于旧的,挂载if (oldChildren.length < newChildren.length) {newChildren.slice(oldChildren.length).forEach((item) => {mount(item, el);});}// 4.3.3 旧的长度多于新的,卸载if (oldChildren.length > newChildren.length) {oldChildren.slice(newChildren.length).forEach((item) => {el.removeChild(item.el);});}} else {/*** 有 key*/// 4.4.1 根据 key 创建一个映射表,方便查找和比较const keyMap = {};oldChildren.forEach((child) => {if (child.props.key) {keyMap[child.props.key] = child;}});// 4.4.2 遍历新的 children 数组newChildren.forEach((newChild, index) => {const oldChild = keyMap[newChild.props.key];if (oldChild) {// 4.4.2.1 如果旧的 children 存在对应的 key,对比并更新子节点patch(oldChild, newChild);oldChildren[index] = oldChild; // 更新旧的 children 数组,方便后续删除处理} else {// 4.4.2.2 如果旧的 children 中没有对应的 key,说明是新增的节点,直接挂载mount(newChild, el, index);}});// 4.4.3 删除旧的 children 中没有对应的 key 的子节点oldChildren.forEach((oldChild) => {if (!oldChildren.find((child) => child.props.key === oldChild.props.key)) {el.removeChild(oldChild.el);}});}}}}
};

相关文章:

mini-vue 的设计

mini-vue 的设计 mini-vue 使用流程与结果预览&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&qu…...

React整理杂记(一)

1.React三项依赖 1.react.js -> 核心代码 2.react-dom.js -> 渲染成dom 3.babel.js->非必须&#xff0c;将jsx转为js 类组件中直接定义的方法&#xff0c;都属于严格模式下 this的绑定可以放到constructor(){}中 2. JSX语法 1.可以直接插入的元素&#xff1a; num…...

[100天算法】-统计封闭岛屿的数目(day 74)

题目描述 有一个二维矩阵 grid &#xff0c;每个位置要么是陆地&#xff08;记号为 0 &#xff09;要么是水域&#xff08;记号为 1 &#xff09;。我们从一块陆地出发&#xff0c;每次可以往上下左右 4 个方向相邻区域走&#xff0c;能走到的所有陆地区域&#xff0c;我们将其…...

esp32-rust-std-examples-blinky

以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例&#xff1a; https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs //! Blinks an LED //! //! This assumes that a LED is connected to GPIO4. //! Depending on your target and the board you are …...

【docker容器技术与K8s】

【docker容器技术与K8s】 一、Docker容器技术 1、Docker的学习路线 &#xff08;1&#xff09;学习Docker基本命令&#xff08;容器管理和镜像管理&#xff09; &#xff08;2&#xff09;学习使用Docker搭建常用软件 &#xff08;3&#xff09;学习Docker网络模式 启动容器的…...

RT-DTER 引入用于低分辨率图像和小物体的新 CNN 模块 SPD-Conv

论文地址:https://arxiv.org/pdf/2208.03641v1.pdf 代码地址:https://github.com/labsaint/spd-conv 卷积神经网络(CNN)在图像分类、目标检测等计算机视觉任务中取得了巨大的成功。然而,在图像分辨率较低或对象较小的更困难的任务中,它们的性能会迅速下降。 这源于现有CNN…...

Folw + Room 实现自动观察数据库的刷新

1、Room &#xff1a;定义数据结构、创建数据库 // 定义实体 Entity data class TestModel ()// 定义数据库 Dao interface TestDao { Query("SELECT * FROM TestTable") fun getAll(): List<TestModel> }// 获取数据库 abstract class TestDatabase: RoomDat…...

黑马程序员微服务Docker实用篇

Docker实用篇 0.学习目标 1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署…...

虚拟化服务器+华为防火墙+kiwi_syslog访问留痕

一、适用场景 1、大中型企业需要对接入用户的访问进行记录时&#xff0c;以前用3CDaemon时&#xff0c;只能用于小型网络当中&#xff0c;记录的数据量太大时&#xff0c;本例采用破解版的kiwi_syslog。 2、当网监、公安查到有非法访问时&#xff0c;可提供基于五元组的外网访…...

FlinkSQL聚合函数(Aggregate Function)详解

使用场景&#xff1a; 聚合函数即 UDAF&#xff0c;常⽤于进多条数据&#xff0c;出⼀条数据的场景。 上图展示了⼀个 聚合函数的例⼦ 以及 聚合函数包含的重要⽅法。 案例场景&#xff1a; 关于饮料的表&#xff0c;有三个字段&#xff0c;分别是 id、name、price&#xff0…...

TensorFlow学习笔记--(3)张量的常用运算函数

损失函数及求偏导 通过 tf.GradientTape 函数来指定损失函数的变量以及表达式 最后通过 gradient(%损失函数%,%偏导对象%) 来获取求偏导的结果 独热编码 给出一组特征值 来对图像进行分类 可以用独热编码 0的概率是第0种 1的概率是第1种 0的概率是第二种 tf.one_hot(%某标签…...

RT-Thread:嵌入式实时操作系统的设计与应用

RT-Thread&#xff08;Real-Time Thread&#xff09;是一个开源的嵌入式实时操作系统&#xff0c;其设计和应用在嵌入式领域具有重要意义。本文将从RT-Thread的设计理念、核心特性&#xff0c;以及在嵌入式系统中的应用等方面进行探讨&#xff0c;对其进行全面的介绍。 首先&a…...

SpringBoot学习笔记-创建菜单与游戏页面(下)

笔记内容转载自 AcWing 的 SpringBoot 框架课讲义&#xff0c;课程链接&#xff1a;AcWing SpringBoot 框架课。 CONTENTS 1. 地图优化改进2. 绘制玩家的起始位置3. 实现玩家移动4. 优化蛇的身体效果5. 碰撞检测实现 本节实现两名玩家即两条蛇的绘制与人工操作移动功能。 1. 地…...

STM32一

0.前言 在B站经常看见有人用stm32做出了有趣的电子小玩艺儿&#xff0c;感到很羡慕&#xff0c;于是想了解一下。 1.什么是stm32 STM32 是一系列由STMicroelectronics&#xff08;意法半导体&#xff09;公司设计和制造的32位ARM Cortex-M微控制器。这一系列的微控制器广泛用…...

GPT-4 Turbo Assistants API

Assistants API Assistants API 允许您在自己的应用程序中构建 AI 助手。助手有指令&#xff0c;可以利用模型、工具和知识来响应用户查询。Assistants API 目前支持三种类型的工具&#xff1a;代码解释器、检索和函数调用。未来&#xff0c;我们计划发布更多 OpenAI 构建的工…...

day08_回顾与课程概括

回顾与课程概括 一、上节课复习 一、上节课复习 1、osi七层与数据传输 2、socketsocket是对传输层以下的封装ipport标识唯一一个基于网络通讯的软件3、tcp与udptcp&#xff1a;因为在通信之前必须建立双向连接&#xff0c;通常都是客户端主动连接服务端的&#xff0c;所以必须…...

iptables、netfilter、firewalld、ufd简单介绍

参考&#xff1a;...

Python基础入门例程53-NP53 前10个偶数(循环语句)

最近的博文&#xff1a; Python基础入门例程52-NP52 累加数与平均值(循环语句)-CSDN博客 Python基础入门例程51-NP51 列表的最大与最小(循环语句)-CSDN博客 Python基础入门例程50-NP50 程序员节&#xff08;循环语句&#xff09;-CSDN博客 目录 最近的博文&#xff1a; 描…...

v-bind和v-model

目录 前言 v-bind 作用 语法格式 编译原理 简写 v-model 作用 使用方法 v-bind和v-model的区别和联系 前言 本文我们来了解一下模板语法之指令语法中的v-bind和v-model v-bind 作用 v-bind可以让html标签的某个属性的值产生动态的效果 语法格式 <html标签 v-bin…...

Adobe premiere裁剪视频尺寸并转为GIF格式

第 1 步&#xff1a;裁剪视频 修改序列设置以适应裁剪之后的图像区域&#xff1b;序列中的编辑模式不能使用默认的&#xff0c;这里使用的是“ProRes RAW” 第 2 步&#xff1a;设置背景色 需要设置“颜色遮罩”的大小和颜色&#xff0c;颜色遮罩放在下面。 第 3 步&#xff1…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

VisualXML全新升级 | 新增数据库编辑功能

VisualXML是一个功能强大的网络总线设计工具&#xff0c;专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑&#xff08;如DBC、LDF、ARXML、HEX等&#xff09;&#xff0c;并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

WebRTC调研

WebRTC是什么&#xff0c;为什么&#xff0c;如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)

零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...

VSCode 没有添加Windows右键菜单

关键字&#xff1a;VSCode&#xff1b;Windows右键菜单&#xff1b;注册表。 文章目录 前言一、工程环境二、配置流程1.右键文件打开2.右键文件夹打开3.右键空白处打开文件夹 三、测试总结 前言 安装 VSCode 时没有注意&#xff0c;实际使用的时候发现 VSCode 在 Windows 菜单栏…...