JavaScript系列(40)--虚拟DOM实现详解
JavaScript虚拟DOM实现详解 🌳
今天,让我们深入了解虚拟DOM的实现原理,这是现代前端框架中非常重要的一个概念,它通过最小化实际DOM操作来提升应用性能。
虚拟DOM基础概念 🌟
💡 小知识:虚拟DOM是真实DOM的JavaScript对象表示,通过比较新旧虚拟DOM的差异来最小化实际DOM操作,从而提升性能。
基本实现 📊
// 1. 虚拟DOM节点实现
class VNode {constructor(type, props, children) {this.type = type; // 节点类型this.props = props; // 节点属性this.children = children;// 子节点this.key = props?.key; // 用于优化的key}
}// 2. 创建虚拟DOM的辅助函数
function createElement(type, props = {}, ...children) {return new VNode(type,props,children.flat().map(child =>typeof child === 'object' ? child : createTextNode(child)));
}function createTextNode(text) {return new VNode('TEXT_ELEMENT', { nodeValue: text }, []);
}// 3. 虚拟DOM渲染器
class VDOMRenderer {constructor(container) {this.container = container;}render(vnode) {this.container.innerHTML = '';const element = this.createDOMElement(vnode);this.container.appendChild(element);}createDOMElement(vnode) {if (vnode.type === 'TEXT_ELEMENT') {return document.createTextNode(vnode.props.nodeValue);}const element = document.createElement(vnode.type);// 设置属性this.updateProps(element, vnode.props);// 递归创建子节点vnode.children.forEach(child => {element.appendChild(this.createDOMElement(child));});return element;}updateProps(element, props) {if (!props) return;Object.entries(props).forEach(([key, value]) => {if (key === 'key') return;if (key.startsWith('on')) {element.addEventListener(key.toLowerCase().slice(2),value);} else if (key === 'style' && typeof value === 'object') {Object.assign(element.style, value);} else if (key === 'className') {element.setAttribute('class', value);} else {element.setAttribute(key, value);}});}
}
高级功能实现 🚀
// 1. 虚拟DOM差异比较算法
class VDOMDiffer {diff(oldVNode, newVNode) {if (!oldVNode) {return { type: 'CREATE', newVNode };}if (!newVNode) {return { type: 'REMOVE' };}if (this.hasChanged(oldVNode, newVNode)) {return { type: 'REPLACE', newVNode };}if (newVNode.type !== 'TEXT_ELEMENT') {const patches = this.diffChildren(oldVNode, newVNode);const props = this.diffProps(oldVNode.props, newVNode.props);if (patches.length || props) {return {type: 'UPDATE',props,patches};}}return null;}hasChanged(oldVNode, newVNode) {return typeof oldVNode !== typeof newVNode ||(oldVNode.type === 'TEXT_ELEMENT' && oldVNode.props.nodeValue !== newVNode.props.nodeValue) ||oldVNode.type !== newVNode.type;}diffProps(oldProps, newProps) {const patches = {};let hasPatches = false;// 检查更新和新增的属性for (const [key, value] of Object.entries(newProps || {})) {if (oldProps?.[key] !== value) {patches[key] = value;hasPatches = true;}}// 检查删除的属性for (const key in oldProps || {}) {if (!(key in newProps)) {patches[key] = null;hasPatches = true;}}return hasPatches ? patches : null;}diffChildren(oldVNode, newVNode) {const patches = [];const maxLength = Math.max(oldVNode.children.length,newVNode.children.length);for (let i = 0; i < maxLength; i++) {const childPatch = this.diff(oldVNode.children[i],newVNode.children[i]);if (childPatch) {patches.push({ index: i, ...childPatch });}}return patches;}
}// 2. 组件生命周期管理
class Component {constructor(props) {this.props = props;this.state = {};this.isMount = false;}setState(newState) {this.state = { ...this.state, ...newState };this.update();}update() {const vnode = this.render();if (this.isMount) {this.componentWillUpdate();this.vdomRenderer.patch(this.vnode, vnode);this.componentDidUpdate();} else {this.componentWillMount();this.vdomRenderer.render(vnode);this.isMount = true;this.componentDidMount();}this.vnode = vnode;}// 生命周期钩子componentWillMount() {}componentDidMount() {}componentWillUpdate() {}componentDidUpdate() {}componentWillUnmount() {}
}// 3. 事件系统实现
class EventSystem {constructor() {this.listeners = new Map();}addEvent(element, eventType, handler) {if (!this.listeners.has(element)) {this.listeners.set(element, new Map());}const elementListeners = this.listeners.get(element);if (!elementListeners.has(eventType)) {elementListeners.set(eventType, new Set());// 添加事件委托element.addEventListener(eventType, (event) => {this.handleEvent(element, eventType, event);});}elementListeners.get(eventType).add(handler);}removeEvent(element, eventType, handler) {const elementListeners = this.listeners.get(element);if (!elementListeners) return;const typeListeners = elementListeners.get(eventType);if (!typeListeners) return;typeListeners.delete(handler);if (typeListeners.size === 0) {elementListeners.delete(eventType);}if (elementListeners.size === 0) {this.listeners.delete(element);}}handleEvent(element, eventType, event) {const elementListeners = this.listeners.get(element);if (!elementListeners) return;const typeListeners = elementListeners.get(eventType);if (!typeListeners) return;typeListeners.forEach(handler => handler(event));}
}
性能优化技巧 ⚡
// 1. 批量更新处理
class BatchUpdateManager {constructor() {this.updates = new Set();this.isPending = false;}addUpdate(component) {this.updates.add(component);this.requestUpdate();}requestUpdate() {if (!this.isPending) {this.isPending = true;Promise.resolve().then(() => this.processBatch());}}processBatch() {const components = Array.from(this.updates);this.updates.clear();this.isPending = false;components.forEach(component => component.update());}
}// 2. 虚拟DOM缓存
class VNodeCache {constructor() {this.cache = new Map();}getKey(vnode) {return JSON.stringify({type: vnode.type,props: vnode.props,children: vnode.children.map(child => this.getKey(child))});}set(vnode, element) {const key = this.getKey(vnode);this.cache.set(key, element);}get(vnode) {const key = this.getKey(vnode);return this.cache.get(key);}clear() {this.cache.clear();}
}// 3. 性能监控
class VDOMPerformanceMonitor {constructor() {this.metrics = {renders: 0,diffs: 0,patches: 0,renderTime: 0,diffTime: 0,patchTime: 0};}startMeasure(operation) {return performance.now();}endMeasure(operation, startTime) {const duration = performance.now() - startTime;this.metrics[`${operation}Time`] += duration;this.metrics[`${operation}s`]++;}getMetrics() {return {...this.metrics,averageRenderTime: this.metrics.renderTime / this.metrics.renders,averageDiffTime: this.metrics.diffTime / this.metrics.diffs,averagePatchTime: this.metrics.patchTime / this.metrics.patches};}reset() {Object.keys(this.metrics).forEach(key => {this.metrics[key] = 0;});}
}
最佳实践建议 💡
- 性能优化策略
// 1. 组件更新优化器
class ComponentOptimizer {shouldComponentUpdate(oldProps, newProps, oldState, newState) {// 浅比较props和statereturn !this.shallowEqual(oldProps, newProps) ||!this.shallowEqual(oldState, newState);}shallowEqual(obj1, obj2) {if (obj1 === obj2) return true;if (!obj1 || !obj2) return false;const keys1 = Object.keys(obj1);const keys2 = Object.keys(obj2);if (keys1.length !== keys2.length) return false;return keys1.every(key => obj1[key] === obj2[key]);}
}// 2. 虚拟DOM优化器
class VDOMOptimizer {static optimizeVNode(vnode) {// 移除空属性if (vnode.props) {Object.keys(vnode.props).forEach(key => {if (vnode.props[key] === undefined ||vnode.props[key] === null) {delete vnode.props[key];}});}// 优化子节点if (vnode.children) {vnode.children = vnode.children.filter(child => child !== null && child !== undefined).map(child => this.optimizeVNode(child));}return vnode;}
}// 3. 内存管理器
class MemoryManager {constructor() {this.pool = new Map();this.maxPoolSize = 1000;}acquireVNode(type, props, children) {const key = this.getKey(type, props);const pool = this.pool.get(key) || [];if (pool.length > 0) {const vnode = pool.pop();vnode.props = props;vnode.children = children;return vnode;}return new VNode(type, props, children);}releaseVNode(vnode) {const key = this.getKey(vnode.type, vnode.props);const pool = this.pool.get(key) || [];if (pool.length < this.maxPoolSize) {pool.push(vnode);this.pool.set(key, pool);}}getKey(type, props) {return `${type}-${props?.key || ''}`;}
}
结语 📝
虚拟DOM是现代前端框架中非常重要的一个概念,通过本文,我们学习了:
- 虚拟DOM的基本概念和实现原理
- 差异比较算法的实现
- 组件生命周期管理
- 事件系统的实现
- 性能优化技巧
💡 学习建议:在实践中,要注意平衡虚拟DOM的更新粒度,避免不必要的重渲染。同时,要善用key属性来优化列表渲染性能。
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
相关文章:
JavaScript系列(40)--虚拟DOM实现详解
JavaScript虚拟DOM实现详解 🌳 今天,让我们深入了解虚拟DOM的实现原理,这是现代前端框架中非常重要的一个概念,它通过最小化实际DOM操作来提升应用性能。 虚拟DOM基础概念 🌟 💡 小知识:虚拟D…...
SpringAI基于API对大语言模型调用
引言 随着人工智能技术的迅猛发展,大型语言模型(LLM)在各个领域的应用越来越广泛。SpringAI作为一个旨在简化AI集成的框架,为开发者提供了高效、便捷的工具来连接和调用这些大模型。本文将详细探讨如何使用SpringAI整合通义千问等…...
0 基础学运维:解锁 K8s 云计算运维工程师成长密码
前言:作为一个过来人,我曾站在技术的门槛之外,连电脑运行内存和内存空间都傻傻分不清,完完全全的零基础。但如今,我已成长为一名资深的k8s云计算运维工程师。回顾这段历程,我深知踏上这条技术之路的艰辛与不…...
在 vscode + cmake + GNU 工具链的基础上配置 JLINK
安装 JLINK JLINK 官网链接 下载安装后找到安装路径下的可执行文件 将此路径添加到环境变量的 Path 中。 创建 JFlash 项目 打开 JFlash,选择新建项目 选择单片机型号 在弹出的窗口中搜索单片机 其他参数根据实际情况填写 新建完成: 接下来设置…...
【全栈】SprintBoot+vue3迷你商城(9)
【全栈】SprintBootvue3迷你商城(9) 往期的文章都在这里啦,大家有兴趣可以看一下 后端部分: 【全栈】SprintBootvue3迷你商城(1) 【全栈】SprintBootvue3迷你商城(2) 【全栈】Spr…...
自动化实现的思路变化
阶段一: 1、成功调用。第一步,一般是用现用的工具,或者脚本成功调用接口 2、解决关联接口的参数传递。有的接口直接,存在参数的传递,一般的思路,就是将这个参数设置为变量。 3、简化代码。总会有些东西是重…...
省市区三级联动
引言 在网页中,经常会遇到需要用户选择地区的场景,如注册表单、地址填写等。为了提供更好的用户体验,我们可以实现一个三级联动的地区选择器,让用户依次选择省份、城市和地区。 效果展示: 只有先选择省份后才可以选择…...
Mac安装Redis并设置launchd自启动
下载和编译redis源码 方便mac同学,不想使用brew方式安装,又想开机自启动redis,简单记录一下。首先下载redis7.0.15.tar.gz源码包 tar -xf tar -zxf redis-7.0.15.tar.gz开始编译源码 cd redis-7.0.15 sudo cp redis.conf /etc/redis.conf …...
Fullcalendar @fullcalendar/react 样式错乱丢失问题和导致页面卡顿崩溃问题
问题描述: 我使用 fullcalendar的react版本时,出现了一个诡异的问题,当我切换到 一个iframe页面时(整个页面是一个iframe嵌入的),再切换回来日历的样式丢失了!不仅丢失了样式还导致页面崩溃了&…...
dm8在Linux环境安装精简步骤说明(2024年12月更新版dm8)
dm8在Linux环境安装详细步骤 - - 2025年1月之后dm8 环境介绍1 修改操作系统资源限制2 操作系统创建用户3 操作系统配置4 数据库安装5 初始化数据库6 实例参数优化7 登录数据库配置归档与备份8 配置审计9 创建用户10 屏蔽关键字与数据库兼容模式11 jdbc连接串配置12 更多达梦数据…...
Linux MySQL离线安装
一、准备工作 1. 下载MySQL安装包 访问MySQL官方网站,选择适合您Linux系统的MySQL版本进行下载。通常推荐下载Generic Linux (glibc 2.12)版本的.tar.gz压缩包,例如mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz。将下载好的安装包拷贝到Linux服务器的某…...
S4 HANA更改Tax base Amount的字段控制
本文主要介绍在S4 HANA OP中Tax base Amount的字段控制相关设置。具体请参照如下内容: 1. 更改Tax base Amount的字段控制 以上配置用于控制FB60/FB65/FB70/FB75/MIRO的页签“Tax”界面是否可以修改“Tax base Amount”, 如果勾选Change 表示可以修改T…...
JVM堆空间
一、堆空间的核心概述 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。 堆内存的大小是可以调节的。堆可以处于物理上不连续的内存空间中ÿ…...
《深入解析:DOS检测的技术原理与方法》
DDOS入侵检测与防御 一、实现Linux下DDOS的入侵检测与防御 利用Python编程实现对wrk的泛洪攻击检测,并让程序触发调用Linux命令实现防御: 1、泛洪攻击的检测,可以考虑使用的命令,这些命令可以通过Python进行调用和分析 (1) netstat -ant …...
PID如何调试,如何配置P,I,D值,如何适配pwm的定时器配置,如何给小车配电源
首先你要搞清楚PID公式原理 PID算法解析PID算法解析_pid滤波算法-CSDN博客 然后你要明白调试原理 首先要确定一个电源 电源决定了你后面调试时电机转动速度大小和pwm占空比的关系,电源电压越大那要转到同一速度所需的占空比越小,反之电源电压越小那要…...
小马模拟器-第三方全街机游戏模拟器
链接:https://pan.xunlei.com/s/VOHSiB6st-f3RWlIK01MS2fUA1?pwd44v7# 1.小马模拟器是一款完全免费的游戏模拟器软件,支持街机(FBA,MAME,PGM2),3DS,WII,NGC,DC,SS,DOS,MD,WSC,NDS,JAVA,PCE,FC,SFC,GBA,GBC,PSP,PS,N64等多种游戏…...
Qwen2-VL:在任何分辨率下增强视觉语言模型对世界的感知 (大型视觉模型 核心技术 分享)
摘要 我们推出了Qwen2-VL系列,这是对之前Qwen-VL模型的高级升级,重新定义了视觉处理中的常规预设分辨率方法。Qwen2-VL引入了Naive Dynamic Resolution机制,使模型能够动态地将不同分辨率的图像转换为不同的视觉令牌数量。这种方法允许模型生成更高效和准确的视觉表示,紧密…...
微信小程序date picker的一些说明
微信小程序的picker是一个功能强大的组件,它可以是一个普通选择器,也可以是多项选择器,也可以是时间、日期、省市区选择器。 官方文档在这里 这里讲一下date picker的用法。 <view class"section"><view class"se…...
MySQL 基础学习(2): INSERT 操作
在这篇文章中,我们将专注于 MySQL 中的 INSERT 操作,深入了解如何高效地向表中插入数据,并探索插入操作中的一些常见错误与解决方案。 一、基础 INSERT 语法 在 MySQL 中,INSERT 操作用于向表中插入新记录,基本语法如…...
关于opensips的帮助命令的解释
opensips -help以下是 opensips 命令及其选项的中文解释(基于 3.6.0-dev 版本): 命令用法 opensips -l 地址 [-l 地址 ...] [选项]选项说明 选项功能-f 文件指定配置文件(默认为 /usr/local//etc/opensips/opensips.cfg&#x…...
新项目传到git步骤
1.首先创建远程仓库,创建一个空白项目,即可生成一个克隆URL,可以是http也可以是SSH,copy下这个地址 2.找到项目的本机目录,进入根目录,打开git bash here命令行 3.初始化: git init 4.关联远程地址: git remote add origin "远程仓库的URL" 5.查看关联 git re…...
【力扣每日一题】LeetCode 2412: 完成所有交易的初始最少钱数
LeetCode 2412: 完成所有交易的初始最少钱数 题目解析 问题描述 给定一个二维数组 transactions,每个元素 transactions[i] [costi, cashbacki] 表示一个交易。对于每笔交易,要求你完成该交易时有足够的初始资金 money,并且交易会减少或增…...
【算法】递归型枚举与回溯剪枝初识
递归型枚举与回溯剪枝初识 1.枚举子集2.组合型枚举3.枚举排列4.全排列问题 什么是搜索?搜索,是一种枚举,通过穷举所有的情况来找到最优解,或者统计合法解的个数。因此,搜索有时候也叫作暴搜。搜索一般分为深度优先搜索…...
pytorch 多机多卡训练方法
在深度学习训练中,使用多机多卡(多台机器和多块 GPU)可以显著加速模型训练过程。 PyTorch 提供了多种方法来实现多机多卡训练,以下是一些常用的方法和步骤: 1. 使用 torch.distributed 包 PyTorch 的 torch.distribut…...
InfiniBand客户端注册机制详解:ib_register_client函数的作用与实现
在Linux内核的InfiniBand(IB)子系统中,ib_register_client函数扮演着至关重要的角色。它允许上层用户(如特定的IB设备驱动程序或相关应用模块)注册为IB客户端,并定义在IB设备添加或移除时应执行的回调函数。这一机制确保了IB设备的动态管理,以及资源的有效分配和回收。本…...
rocketmq-product-send方法源码分析
先看有哪些send方法 首先说红圈的 有3个红圈。归类成3种发送方式。假设前提条件,发送的topic,有3个broker,每个broker总共4个write队列,总共有12个队列。 普通发送。负载均衡12个队列。指定超时时间指定MessageQueue,发送&#…...
centos下设置服务器开机自启动 redis
在客户服务器中,服务器重启,发现 Redis 没有重启, 可以按照类似的步骤来创建自启动脚本,并将它添加到定时任务中。 解决办法: 1. 创建自启动脚本 进入服务器并创建脚本文件,例如 /usr/local/bin/redis_…...
【Linux】APT 密钥管理:官方推荐的解决方案应对 apt-key 弃用
引言 在 Ubuntu 和 Debian 系统中,apt-key 命令用于管理 GPG 密钥,验证来自软件包存储库的包是否合法并且未被篡改。然而,从 Debian 11 和 Ubuntu 22.04 开始,apt-key 被弃用,并将在未来的版本中完全移除。因此&#…...
69.在 Vue 3 中使用 OpenLayers 拖拽实现放大区域的效果(DragPan)
引言 在现代 Web 开发中,地图功能已经成为许多应用的重要组成部分。OpenLayers 是一个功能强大的开源地图库,支持多种地图源和交互操作。Vue 3 是一个流行的前端框架,以其响应式数据和组件化开发著称。本文将介绍如何在 Vue 3 中集成 OpenLa…...
77,【1】.[CISCN2019 华东南赛区]Web4
有句英文,看看什么意思 好像也可以不看 进入靶场 点击蓝色字体 我勒个豆,百度哇 所以重点应该在url上,属于任意文件读取类型 接下来该判断框架了 常见的web框架如下 一,Python 框架 1.Flask URL 示例 1:http://…...
