手写现代前端框架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代理&#…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...