前端知识体系梳理Diff策略
Posted 摆烂到巅峰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端知识体系梳理Diff策略相关的知识,希望对你有一定的参考价值。
目录
🍉前言
React最为核心的就是虚拟DOM和Diff算法;
React在内存中维护一颗虚拟DOM树,当数据发生改变时,会自动的去更新虚拟DOM,获得一个新的虚拟DOM,然后通过Diff算法,比较新旧虚拟DOM树,找出最小的有变化的部分,将这个变化的部分(Patch)加入队列,最终批量的更新这个Patch到实际DOM中;
🍉一、传统Diff算法
传统的Diff算法的主要思想就是如何用最小的操作步数去修改DOM树;
将一颗树通过最小操作步数映射到另一颗t树,这种算法叫做树编辑距离算法;
这种传统Difff算法的最大缺点就是慢,性能极低,它的时间复杂度只能达到O(n^3);
🍉二、React Diff
diff算法的本质就是:找出两个对象之间的差异,目的是尽可能做到节点复用。
为了优化传统的diff算法,React提出了两个假设:
🌾🌾1、两个不同类型的元素会产生出不同的树
🌾🌾2、开发者可以通过key props 来暗示哪些子元素在不同的渲染下能保持稳定
基于这上述两个假设,React针对性的提出了三个策略以对diff算法进行优化:
🌾🌾1、DOM节点跨层级的移动操作特别少,可以忽略不记
🌾🌾2、拥有相同类型的两个组件将会生成相似的树形结构,拥有不同类型的两个组件将会生成不同树形结构
🌾🌾3、对于同一层级的一组子节点,它们可以通过唯一key进行区分
基于上述三个策略,react分别对以下三个部分进行了diff算法优化
🍓🍓🍓1、tree diff
React只对虚拟DOM树进行分层比较,不考虑节点的跨层级比较,根据对比结果,进行节点的新增和删除,如此,只需要遍历一次虚拟DOM树,就可以完成整个对比;
但是,如果出现了跨层级的操作,react是无法复用已有的节点,而是要整个删除,并重新创建,这会影响性能;所以react官方推荐尽量避免跨层级的操作;
🍓🍓🍓2、component diff
React是基于组件构建的,对于组件间的比较所采用的的策略如下:
🌼1)、如果是同类型组件,首先使用shouldComponentUpdate()方法判断是否需要进行比较,如果返回为true,才比较对应的DOM节点,否则不需要比较
🌼2)、如果是不同类型的组件,则将该组件判断为dirty component,从而替换整个组件下的所有子节点;
比如说,现有两个组件一个A,一个B,虽然这两个组件结构相似,但是类型不同,React不会进行比较,会直接删除组件A,创建组件B;
🌼1)、对于不同类型的组件,默认不需要进行比较操作,直接重新创建
🌼2)、对于同类型的组件,通过让开发人员自定义shouldComponentUpdate()方法来进行比较优化,减少组件不必要的比较。如果没有自定义,shouldComponent()方法默认返回true,默认每次组件发生数据变化时都进行比较
🍓🍓🍓3、element diff
element diff涉及三种操作:移动、创建、删除;对于同一层级的子节点,对于是否使用key分别进行讨论
🌾🌾🌾1)、不使用key的情况:
React对新旧同一层级的子节点对比,发现新集合中的B不等于老集合中的A,于是删除A,创建B,依次类推,这样的话,前后相同的节点,都会因为顺序的不同而被删除和重新创建,从而影响性能;
🌾🌾🌾2)、使用key的情况:
React首先会对新集合进行遍历,通过唯一key来判断老集合中是否存在相同的节点,如果没有则创建,如果存在,则判断是否需要进行移动操作,并且react对于移动操作也采取了一种很高效的算法;
element diff就是通过唯一key来进行diff优化,通过复用已有的节点,减少节点的删除和创建操作;
React通过指定对应的diff策略,将O(n^3)复杂度问题转换成O(n)的复杂度问题
🌼1)、通过分层对比策略,对tree diff进行算法优化
🌼2)、通过相同类生成相似树形结构,不同类生成不同树形结构以及shouldComponentUpdate策略,对Component diff进行算法优化
🌼3)、通过设置唯一key策略,对element diff进行算法优化
🍉三、fiber架构
fiber是React16之后的一个虚拟DOM思想:
fiber可以理解为一个执行单元,每次执行完一个执行单元,react就会检查现在还剩多少时间,如果没有时间则将控制权让出去;
首先React向浏览器请求调度,浏览器在一帧中如果还有空闲时间,会去判断是否存在待执行的任务,不存在就直接将控制权交给浏览器,如果存在就会执行对应的任务,执行完成后回去判断是否还有事件,有时间且有待执行任务则会继续执行下一个任务,否则就会将控制权交给浏览器
组织一个单元需要一个数据结构,用传统的虚拟DOM很难再分割,我们现在构造一个新的结构,我们称之为fiber;
React Fiber就是采用链表实现的,每个虚拟DOM都可以表示为一个fiber;
fiber内容比较多,这里就不多论述,后续会专门出一篇关于fiber的blog;
🍓结束语🏆
🌾🌾🌾明天写webpack相关内容!!!
js知识体系的梳理一
今天简单的总结了js的一些东西,梳理下整个体系,每一次的总结都会有不同的收获;
js总结一
一、【获取元素】:
1、通过ID: var oBtn=document.getElementById(‘btn1‘);
var oDiv=document.getElementById(‘div1‘);
2、通过标签:var aDiv=document.getElementsByTagName(‘div‘);
操作一组元素就要用循环:
for(初始值;循环条件;自增自减条件){} 有长度的用for循环
初始值;while(循环条件){代码;自增自减条件;} 没有长度的用while循环;
for in循环:对象中使用:json arr arr中一般用上边的for循环性能高;
3、通过class名: var aDiv=document.getElementsByClassName(‘div1‘);
有兼容性的问题:通过封装函数来解决:
getByClass(oBox,‘div1‘);
function getByClass(obj,sName){
var aObj=obj.getElementsByTagName(‘*‘);
var arr2=[];
for(var i=0;i<aObj.length;i++){
var str=aObj[i].className;
var arr=str.split(‘ ‘);
if(findInArr(sName,arr)){
arr2.push(aObj[i]);
};
}
return arr2;
}
注:通过Id是获取到一个元素,其他两个获取的都是一组元素,
id只能在document下获取,其他两个方法可以在父级下获取;
id和tagName全兼容,通过类名获取有兼容性问题
获取出来的一组与元素是类数组 可意识使用长度aDiv.length 和 下标aDiv[0] 不能使用数组的其他方法;
二、【事件】:
系统事件:onload\DOMContentLoaded\onreadystatechange\onscroll\onresize
鼠标事件:onclick\onmouseover\onmouseout\onmousedown\onmouseup\onmousemove\onmousewheel\DOMMouseScroll\ oncontextmenu\
键盘事件:onkeydown\onkeyup\
oninput\onpropertychange\
*****事件对象:存储事件发生的详细信息;声明 var oEvent=ev||event;
事件委托:给父级加事件,判断事件源,执行某项代码;可以给未来的元素加事件;
事件绑定:obj.addEventListener(‘不带on的事件名‘,fn,false);兼容高级浏览器
DOM事件必须绑定;且DOM事件只能在高级浏览器中兼容;
obj.attachEvent(‘带on的事件名‘,fn);兼容IE浏览器;
事件冒泡:从下往上一级一级传递的事件流,一般不阻止冒泡,有问题才阻止;
oEvent.cancelBubble=true;
事件下沉:与事件冒泡相反的事件流,从上往下一级一级传递;
常见的对象信息 oEvent.target||oEvent.srcElement;
oEvent.clientX;/oEvent.clientY;
oEvent.keyCode/oEvent.shiftKey/oEvent.altKey/oEvent.ctrlKey;
oEvent.wheelDelta/oEvent.detail
三、【函数】:
1、命名函数:function show(){} 出现 show();的时候执行;
2、匿名函数:functionz(){}; 加括号时执行,如封闭空间;
封闭空间:(function(index){})(i); 1)解决变量冲突;2)解决i的问题;3)不用单独起名字;
3、事件函数:obj.onclick=function(){}; 事件触发时执行;
注:函数的返回值:return 可写可不写,需要时写,见return直接跳出函数后面的代码不执行了;函数如果没有写返 回值;那返回的就是undefined;
函数的定义存在预解析:在if 和 for 循环中不能直接定义函数;
四、【流程控制】:
1、if(){}else if(){}else{}; 只有第一个if是必须的 后边的需要是用;
2、switch(条件){
case 条件一:
代码;
break;
case 条件二:
代码;
break;
default:
代码;
break;
};
五、【循环】:
1、for(初始值;循环条件;自增自减条件){} 有长度的用for循环; for循环中直接套for循环时注意变量不能一样
2、初始值;while(循环条件){代码;自增自减条件;} 没有长度的用while循环;
3、for in循环:对象中使用:json arr arr中一般用上边的for循环性能高;
注:循环中如果有事件 事件中的i有问题,用this或封闭空间来解决(i只要赋值时间与调用时间存在时间差就有问题)
break/continue:大多数都用在循环中,break 是直接跳出循环,continue 是跳过当次循环;
应用:选项卡 简易日历 九九乘法表
五、【操作属性和样式】:
./[] 点能操作的中括号都能操作,点后不能跟变量,中括号中可以加变量;操作的都是行间样式;
getAttribute(‘属性名‘)/setAttribute(‘属性名‘,‘属性值‘);
应用:懒加载
六、【变量】:
1、声明变量:var 变量名=‘变量值‘; 存在预解析的问题;定义被放到作用域的最前边;面试常考
严格模式下:变量的定义必须加var
2、非严格模式下:var a=b=c=8; 第一个变量是局部变量 后两个是全局变量;
3、变量使用时不加引号;字符串加引号;
七、【数据类型】:
1、 string 字符串 使用时加引号;字符串的拼接 ‘字符串‘+变量+‘字符串‘+变量;
字符串的方法:
str.charAt(下标):查找下标对应的字符串;
str.indexOf(‘字符串‘):从左往右查找字符串所对应的下标;找不到时返回-1;
str.lastIndexOf(‘字符串‘) 从右往左查找字符串所对应的下标;
str.split(‘切割方法‘):将字符串以括号中的方式切割成数组;
str.substring(开始的位置,结束的位置):截取从开始到结束位置的字符串;
str.toUpperCase(); 字符串中的字母全部转换成大写;
str.toLowerCase(); 字符串中的字母全部转换成小写;
字符串转数字:parseInt();转换成整数 数字后边可以跟字符串
parseFloat();转换成小数 数字后边可以跟字符串;
Number();严格转换 字符串中只用是数字;
转换失败返回NaN NaN代表的意思是不是一个字母,但它的数据类型是numberNaN不等于任何东西包括它自己
判断是不是NaN 用isNaN();
比较大小:
1) 两个都是数字时正常比较;
2) 一个数字一个数字类型的字符串;先隐式类型转换 再按数字大小进行比较
3) 两个都是数字类型的字符串则从左往右一个一个比较 例如 ‘2‘>‘12‘;
4) 两个都是完全的字符串,则从左往右一个一个比较 a<z;a<A;
应用:字符串首字母转大写 判断浏览器类型
2、number 数字
1)数字加数字是算数运算;数字加字符串 则是字符串拼接;+号没有隐式类型转换;
2)在运算符中除了+ = === 外其他大多数都存在隐士类型转换
3)常用的数学方法
Math.random()随机数
Math.abs() 绝对值
Math.sqrt() 开方
Math.pow(n,m) n的m次方
Math.ceil() 向上取整
Math.floor() 向下取整
Math.round() 四舍五入
Math.max() 最大值
Math.min() 最小值
4)运算符:
算术运算符:+、-、*、/、%;
赋值运算符:=、+=、-=、*=、/=、%=;
比较运算符:==、>、>=、<、<=、===、!=、!==
逻辑运算符:&&、||、!
应用:计算器 随机数获取 瀑布流
3、Boolean 布尔值
true: 除了false都是真的;
false:false\0\‘‘\undefined\NaN\null;都是假的
4、object 对象
数组:
声明:var arr=[]/ var arr=new Arrey(); 数组中可以放任何数据类型;有序、有长度
数组项的添加、替换:arr[1]=‘blablabla‘;
数组的方法:
arr.push() 向后添加一项;
arr.unshift() 向前添加一项;
arr.pop() 从后边删除一项;
arr.shift() 从前边删除一项;
arr.splice(开始的位置,删除的个数,添加项);
arr.reverse() 翻转;
arr.join(‘拼接方式‘) 将数组中的每一项拼接成一个字符串;
arr.sort(function(n,m){return n-m}); 排序 里面参数和返回值顺序相同时是从小到达,反之是从大到小
arr.concat(arr1,arr2) 数组的拼接;
注:数组可以用for循环 也可以用for in 循环 一般情况下用for 循环 性能高;
应用:字符串翻转 排序 数据存储调用
json:
声明:var json={‘‘:‘‘,‘‘:‘‘,‘‘:‘‘};无序、无长度、取值用json.name;
json中添加项 json.name=‘val‘/json[‘name‘]=‘val‘;
替换项 上述同样的方法:用一样的键名
删除项 delete json.name/delete json[‘name‘];
json的循环只能用for in for(name in json){} name 表示键名;
应用:json 数组去重 json[arr[i]]=‘luanqibazao‘;
setStyle();
getPos();
数据存储与调用;交互eval(‘(‘+json+‘)‘);
以上是关于前端知识体系梳理Diff策略的主要内容,如果未能解决你的问题,请参考以下文章