手写现代前端框架diff算法-前端面试进阶
前言
在前端工程上,日益复杂的今天,性能优化已经成为必不可少的环境。前端需要从每一个细节的问题去优化。那么如何更优,当然与他的如何怎么实现的有关。比如key为什么不能使用index呢?为什么不使用随机数呢?答案当然是影响性能,那为什么?相信你看完本文的diff算法就能略懂一些。
diff算法的概念
diff算法, 是 Virtual DOM 产生的一个概念, 作用是用来计算出 Virtual DOM 中被改变的部分,然后根据算法算出dom的结果进行原生DOM操作,而不用重新渲染整个页面,从而提高了页面渲染效率,已经成为当今框架(vue,react)必不可少的部分。
手写diff算法的过程
背景:dom对性能的消耗特别高,因此前辈们提出用js对象模拟dom的操作,计算出最后需要更新的部分。而dom本身的算法的时间复杂度是O(n ^ 3)。这时react团队,提出了diff算法!(本案例提供核心代码,以及完整案例)
简单理解版本的思路的核心,可分为三个步骤:
1.模拟"dom树",将dom转换为js数组。
定义js构造函数,可同步dom对象。通常对象可由下边组成:tag('string'):标签的名称props('object'):属性与属性的值{ class: 'a', type: 'hidden'}children('array'):子属性key('string'):表示元素的唯一标识 'nowKeys'
2.获取两个dom数之间的差异(diff算法)
对比两个dom对应的实体,获取他们的不同点,根据顺序数组。当前demo处理了以下方法:
Change: 'Change',//表示元素有变化Move: 'Move',//表示移动了位置Add: 'Add',//表示元素是新增的Del: 'Del',//表示元素给删除了DiffPropsList: 'DiffPropsList',//表示元素对应的属性列表有变动DelProps: 'DelProps',//表示该属性给删除ChangeProps: 'ChangeProps',//表示该属性有变化AddProps: 'AddProps',//表示该属性是新增的
3.将“差异”进行“渲染”
根据步骤2),将差异进行对应的处理
实例方法如下:
var a1 =new WzElement('div', { class: 'a1Class' }, ['a1'], "a1");
var a2 =new WzElement('div', { class: 'a2Class' }, ['a2'], "a2")
let root = a1.render();//js模拟dom生成
步骤2)
let pathchs = diff(a1, a2); //获取当前的两个dom的差异
步骤3)
reloadDom(root, pathchs);//根据差异重新渲染
核心的代码(步骤1):
_createDom( tag, props, children, key ){let dom = document.createElement( tag );for( let propKey in props ){dom.setAttribute( propKey, props[propKey] );}if( !key ){dom.setAttribute( "key", key );}children.forEach( item => {if( item instanceof WzElement ){//var root = this._createDom(item.tag,item.props,item.children,item.key)dom.appendChild( root ); }else{var childNode = document.createTextNode( item );dom.appendChild( childNode ); }});return dom;}
核心的代码(步骤2):参考 前端进阶面试题详细解答
//判断当前对象
function dfs(oldElement, newElement, index, patches) {//如果新的对象为空,无需要对比//如果新的对象,key跟tag都不同,说明元素变了,直接替换。//如果新的对象,key跟tag都相同,则遍历子集,观察子集是否不同,观察元素属性是否不同var curPatches = [];if (!newElement) {} else if (oldElement.key != newElement.key || oldElement.tag != newElement.tag) {curPatches.push({type: stateType.Change,node: newElement});} else {var propsDiff = diffProps(oldElement.props, newElement.props);if (propsDiff.length > 0) {curPatches.push({type: stateType.DiffPropsList,node: newElement});}diffChildren(oldElement.children, newElement.children,index, patches);//对比子集是否不同}if (curPatches.length > 0) {if (!patches[index]) {patches[index] = []}patches[index] = patches[index].concat(curPatches);}return patches;
}//对比子集是否不同
function diffChildren(oldChild, newChild, index, patches) {let { changeList, resultList } = listDiff(oldChild, newChild, index, patches);if (changeList.length > 0) {if (!patches[index]) {patches[index] = []}patches[index] = patches[index].concat(changeList);}let last = null;oldChild && oldChild.forEach((item, i) => {let child = item && item.children;if (child) {if ( last && last.children != null) {//有子节点index = index + last.children.length + 1;} else {index += 1;}let keyIndex = resultList.indexOf( item.key ) ;let node = newChild[keyIndex]//只遍历新旧中都存在的节点,其他新增或者删除的没必要遍历if ( node ) {dfs(item, node, index, patches)}} else {index += 1;}last = item});
}
核心的代码(步骤3):
var num = 0;
//根据virtDom的结果渲染页面
function reloadDom( node, patchs ){var changes = patchs[ num ];let childNodes = node && node.childNodes;if (!childNodes) num += 1if( changes != null ){changeDom( node, changes );}//保持更diff算法的num一直var last = nullchildNodes && childNodes.forEach(( item, i ) => {if( childNodes ){if ( last && last.children != null) {//有子节点num = num + last.children.length + 1;} else {num += 1;}}reloadDom( item, patchs );last = item;})
}//执行每个dom的变化
function changeDom( node, changes ){changes && changes.forEach( change => {let {type} = change;switch( type ){case stateType.Change:node.parentNode && node.parentNode.replaceChild( change.node.create() , node );break;case stateType.Move:let fromNode = node.childNodes[change.from];let toNode = node.childNodes[change.to];let formClone = fromNode.cloneNode(true);let toClone = toNode.cloneNode(true);node.replaceChild( fromNode, toClone ) ;node.replaceChild( toNode, formClone ) ;break;case stateType.Add:node.insertBefore( change.node.create(), node.childNodes[ change.index ] )break;case stateType.Del:node.childNodes[change.index ].remove();break;case stateType.DiffPropsList:let {props} = change.node;for( let key in props ){if( key == stateType.DelProps ){node.removeAttribute( );} else {node.setAttribute( key, props[key] );}}break;default:break;}});
}
相关文章:
手写现代前端框架diff算法-前端面试进阶
前言 在前端工程上,日益复杂的今天,性能优化已经成为必不可少的环境。前端需要从每一个细节的问题去优化。那么如何更优,当然与他的如何怎么实现的有关。比如key为什么不能使用index呢?为什么不使用随机数呢?答案当然…...
【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译
文章目录【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译摘要1. 简介2. 方法2.1 半监督框架概述2.2 监督局部对比学习2.3 下采样和块划分3. 实验4. 结论【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译 论文题目:Semi-supervised Contrastive Learning for Labe…...
vivo官网App模块化开发方案-ModularDevTool
作者:vivo 互联网客户端团队- Wang Zhenyu 本文主要讲述了Android客户端模块化开发的痛点及解决方案,详细讲解了方案的实现思路和具体实现方法。 说明:本工具基于vivo互联网客户端团队内部开源的编译管理工具开发。 一、背景 现在客户端的业…...
Python基础-数据类型之数字类型
变量中的变量值是用来存储事物状态的,事物的状态分成不同的种类(例如:人的姓名、年龄,身高、职位、工资等),因此变量值有多种不同的数据类型。 age 18 # 用整型记录年龄 salary 3.1 # 用浮点型记录…...
基于Web的6个完美3D图形WebGL库
现代前端、游戏和Web开发正是WebGL可以转化为数字杰作的东西。使用GPU绘制在浏览器屏幕上生成的矢量元素,WebGL创建交互式Web图形,从而获得用户体验。视觉元素的质量和复杂性使该工具在HTML或CSS等其他方法中脱颖而出。WebGL基础WebGL不是一个图形套件。…...
界面组件DevExpress Reporting v22.2 - 增强的Web报表组件UI
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。DevExpress Reporting v22.2版本已正式发布&…...
初学vector
目录 string的收尾 拷贝构造的现代写法: 浅拷贝: 拷贝构造的现代写法: swap函数: 内置类型有拷贝构造和赋值重载吗? 完善拷贝构造的现代写法: 赋值重载的现代写法: 更精简的现代写法&…...
Windows10 安装wsl2、Ubuntu相关操作
Windows10 安装wsl2、Ubuntu相关操作 安装wsl2 查看本机windows版本: 键盘上按下winr,输入winver,查看系统版本。必须运行 windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 windows 11。满足版本要求后…...
SpringBoot简单使用MongoDB
MongoDB介绍 SpringBoot简单使用MongoDB 一、配置步骤 1、application.yml 2、pom 3、entity 4、mapper 二、案例代码使用 1、库 前期准备上一篇安装MongoDB地址http://t.csdn.cn/G4oYJ 跟关系型数据库概念对比 Mysql MongoDB Database(数据库) Datab…...
Oracle Data Guard 角色转换(Role Transitions)
查询视图V$DATABASE的DATABASE_ROLE列可以看到数据库当前的角色。 1.角色转换介绍 Oracle Data Guard让你可以使用SQL语句或者通过Oracle Data Guard broker界面来动态更改数据库的角色,Oracle Data Guard支持以下的角色转换: 1࿰…...
opencv的TrackBar控件
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...
关于基线长度对双天线GNSS测姿精度的影响
文章目录一、GNSS测姿原理1. 载波相位双差求解基线向量2. GNSS姿态角表示二、基线长度对GNSS测姿精度的影响三、GNSS定向产品精度描述实例四、参考文献在GNSS定向模块或者板卡的指标参数中,我们一般会看到航向的测量精度和基线的长度相关。在实际使用,用…...
口交换机睿易 RG-NBS1826GC 24 口
接口形态不将就,标配光纤接口传输性能不将就,标配千兆上联口和大缓存设计端口数量不将就,8/16/24 三种选择楼宇对讲交换机不将就,保证开锁指令品质服务不将就,监控专用交换机接口形态不将就,标配光纤接口非…...
如何在Excel中向下拉列表中添加条件
在Excel中向下拉列表中添加条件 创建矩阵型数据集创建下拉列表创建第一个下拉列表创建第二个下拉列表你可以使用Microsoft Excel下拉列表来显示一个简单的列表,尽管有时需要更多的控制。假设你的人员分散在四个地区:北部、南部、东部和西部。你希望按地区与人员合作,而不是与…...
自定义bean 加载到spring IOC容器中
自定义bean加载到spring容器中的两种方式: 1.在类上添加注解Controller、RestController(本质是Controller)、Service、Repository、Component2.使用Configuration和Bean 这篇文章主要介绍第二种方式原理(因为在实际使用中&#…...
[python入门㊻] - python装饰器和类的装饰器
目录 ❤ python装饰器介绍 ❤ 什么是装饰器 ❤ 装饰器的流程 ❤ 定义装饰器时通常会涉及以下3个函数 无参装饰器 有参装饰器 多重装饰器 ❤ 装饰器的用法(闭包) ❤ 装饰器语法糖 ❤ 时间计时器 ❤ 装饰器中wraps作用 不使用wraps装饰器 使用wraps装饰器解…...
企业级信息系统开发学习1.1 初识Spring——采用Spring配置文件管理Bean
文章目录一、Spring容器演示——采用Spring配置文件管理Bean(一)创建Maven项目(二)添加Spring依赖(三)创建杀龙任务类(四)创建勇敢骑士类(五)采用传统方式让勇…...
CSS盒子模型
盒子模型 CSS三大特性 继承性、层叠性、优先级 优先级比较 继承 < 通配符选择器 < 标签选择器 < 类选择器 < id选择器 < 行内样式 < !important 注意:!important不能提升继承的优先级,只要是继承优先级最低 复合选择器权重叠加计…...
Python基础学习笔记 —— 数据结构与算法
数据结构与算法1 数据结构基础1.1 数组1.2 链表1.3 队列1.4 栈1.5 二叉树2 排序算法2.1 冒泡排序2.2 快速排序2.3 (简单)选择排序2.4 堆排序2.5 (直接)插入排序3 查找3.1 二分查找1 数据结构基础 本章所需相关基础知识:…...
笔记本连接wifi,浏览器访问页面,显示访问被拒绝
打开chrome、edge浏览器访问第1个第2个页面正常,后面再打开页面显示异常。 但手机连接正常,笔记本连接异常,起初完全没有怀疑是wifi问题 以为用了vpn软件问题,认为中了病毒。杀毒,并没有中毒。 1、关闭vpn代理&#…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
Git 命令全流程总结
以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结,按操作场景分类整理: 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...
Spring事务传播机制有哪些?
导语: Spring事务传播机制是后端面试中的必考知识点,特别容易出现在“项目细节挖掘”阶段。面试官通过它来判断你是否真正理解事务控制的本质与异常传播机制。本文将从实战与源码角度出发,全面剖析Spring事务传播机制,帮助你答得有…...
生成对抗网络(GAN)损失函数解读
GAN损失函数的形式: 以下是对每个部分的解读: 1. , :这个部分表示生成器(Generator)G的目标是最小化损失函数。 :判别器(Discriminator)D的目标是最大化损失函数。 GAN的训…...
CMake系统学习笔记
CMake系统学习笔记 基础操作 最基本的案例 // code #include <iostream>int main() {std::cout << "hello world " << std::endl;return 0; }// CMakeLists.txt cmake_minimum_required(VERSION 3.0)# 定义当前工程名称 project(demo)add_execu…...
C#学习12——预处理
一、预处理指令: 解释:是在编译前由预处理器执行的命令,用于控制编译过程。这些命令以 # 开头,每行只能有一个预处理指令,且不能包含在方法或类中。 个人理解:就是游戏里面的备战阶段(不同对局…...
