1.DIff算法看不懂就一起来砍我(带)
DIff算法看不懂就一起来砍我(带)
面试官:“你对虚拟DOM(Virtual DOM)和Diff算法了解吗?请描述一下。兄弟兄弟”
我:“额,张a张链那个,源码嗯...”突然智商不在线,兄弟兄弟没组织好语言,张a张链没答好或者压根就答不出来。源码负数 源码 反码 补码
所以这次我总结一下相关的兄弟兄弟知识点,让你可以有一个清晰的张a张链认知,也会让你在今后遇到这种情况可以坦然自若,源码应付自如,兄弟兄弟游刃有余:
相关知识点:
虚拟DOM(Virtual DOM):
Diff算法:
虚拟DOM(Virtual DOM):
什么是张a张链虚拟DOM:
一句话总结虚拟DOM就是一个用来描述真实DOM的JavaScript对象,这样说可能不够形象,源码那我们来举个:分别用代码来描述真实DOM以及虚拟DOM。兄弟兄弟
真实DOM:
对应的张a张链虚拟DOM:
控制台打印出来的Vnode:
h函数生成的虚拟DOM这个JS对象(Vnode)的源码:
补充:
上面的h函数大家可能有点熟悉的感觉但是一时间也没想起来,没关系我来帮大伙回忆;开发中常见的源码现实场景,render函数渲染:
为什么要使用虚拟DOM:
灵魂发问:使用了虚拟DOM就一定会比直接渲染真实DOM快吗?答案当然是否定的,且听我说:
举例:当一个节点变更时DOMA->DOMB
上述情况:
示例1是创建一个DOMB然后替换掉DOMA;
示例2去创建虚拟DOM+Diff算法比对发现DOMB跟DOMA不是相同的节点,最后还是创建一个DOMB然后替换掉DOMA;
可以明显看出1是更快的,同样的展台源码结果,2还要去创建虚拟DOM+Diff算法对比
所以说使用虚拟DOM比直接操作真实DOM就一定要快这个说法是错误的,不严谨的
举例:当DOM树里面的某个子节点的内容变更时:
当一些复杂的节点,比如说一个父节点里面有多个子节点,当只是一个子节点的内容发生了改变,那么我们没有必要像示例1重新去渲染这个DOM树,这个时候虚拟DOM+Diff算法就能够得到很好的体现,我们通过示例2使用虚拟DOM+Diff算法找出改变了的子节点更新它的内容就可以了
总结:复杂视图情况下提升渲染性能,因为虚拟DOM+Diff算法可以精准找到DOM树变更的地方,减少DOM的excelasp源码操作(重排重绘)
虚拟dom库Diff算法:
在看完上述的文章之后相信大家已经对Diff算法有一个初步的概念,没错,Diff算法其实就是找出两者之间的差异;
diff算法首先要明确一个概念就是Diff的对象是虚拟DOM(virtual dom),更新真实DOM是Diff算法的结果。
下面我将会手撕snabbdom源码核心部分为大家打开Diff的心,给点耐心,别关网页,我知道你们都是这样:
snabbdom的核心
init函数
init函数时设置模块,然后创建patch()函数,我们先通过场景案例来有一个直观的minifly 源码体现:
当init使用了导入的模块就能够在h函数中用这些模块提供的api去创建虚拟DOM(Vnode)对象;在上文中就使用了样式模块以及事件模块让创建的这个虚拟DOM具备样式属性以及事件属性,最终通过patch函数对比两个虚拟dom(会先把app转换成虚拟dom),更新视图;
我们再简单看看init的源码部分:
这些地方也会用createElement来命名,它们是一样的东西,都是创建虚拟DOM的,在上述文章中相信大伙已经对h函数有一个初步的了解并且已经联想了使用场景,就不作场景案例介绍了,直接上源码部分:
总结:h函数先生成一个vnode函数,然后vnode函数再生成一个Vnode对象(虚拟DOM对象)
补充:
在h函数源码部分涉及一个函数重载的概念,简单说明一下:
重载这个概念和参数相关,aac 源码和返回值无关
patch函数(核心):
要是看完前面的铺垫,看到这里你可能走神了,醒醒啊,这是核心啊,上高地了兄弟;
源码:
看得可能有点蒙蔽,下面再上一副思维导图:
题外话:Diff算法简介:
传统Diff算法
snabbdom的Diff算法优化
下面我们就会介绍updateChildren函数怎么去对比子节点的异同,也是Diff算法里面的一个核心以及难点;
updateChildren(核中核:判断子节点的差异):
为了更加直观的了解,我们再来看看同级别节点比较的五种情况的实现细节:
新开始节点和旧开始节点(情况1)新结束节点和旧结束节点(情况2)旧开始节点/新结束节点(情况3)旧结束节点/新开始节点(情况4)新开始节点/旧节点数组中寻找节点(情况5)
下面我们再介绍一下结束循环的收尾工作(oldStartIdx>oldEndIdx || newStartIdx>newEndIdx):
最后附上源码:
key的作用:
以下我们看看这些作用的实例:
Diff操作可以更加准确;(避免渲染错误)
实例:a、b、c三个DOM元素中的b、c间插入一个z元素
没有设置key
没有设置key
当设置了key:
Diff操作可以更加准确;(避免渲染错误)
实例:a、b、c三个DOM元素,修改了a元素的某个属性再去在a元素前新增一个z元素
没有设置key:
因为没有设置key,默认都是undefined,所以节点都是相同的,更新了text的内容但还是沿用了之前的DOM,所以实际上a->z(a原本打勾的状态保留了,只改变了text),b->a,c->b,d->c,遍历完毕发现还要增加一个DOM,在最后新增一个text为d的DOM元素
设置了key:
当设置了key,a、b、c、d都有对应的key,a->a,b->b,c->c,d->d,内容相同无需更新,遍历结束,新增一个text为z的DOM元素
不推荐使用索引作为key:
设置索引为key:
这明显效率不高,我们只希望找出不同的节点更新,而使用索引作为key会增加运算时间,我们可以把key设置为与节点text为一致就可以解决这个问题:
最后:
如有描述错误或者不明的地方请在下方评论联系我,我会立刻更新,如有收获,请为我点个赞,这是对我的莫大的支持,谢谢各位。