前言
现在社会各行各业大都面临着寒冬,互联网行业最近还出现了裁员潮,导致前端是越来越卷,普通学校的应届生不仅要跟985、211毕业的学生以及研究生进行竞争,甚至还需要和最近刚被裁的、有了几年工作经验的程序员竞争,害,趁我还年轻,赶紧努力考个公吧😭
玩笑归玩笑,但是现在就业形势确实严峻,前端面试时考查的难度也越来越大,面试官会问很多关于框架原理的知识点以区分候选人的水平和学习能力,而 diff
算法就属于面试中的高频问题,下面我们就来了解一下 diff
算法的核心思想,希望能让大家再被问到 diff
算法的时候能更加自信~
虚拟 DOM
在学习 diff
算法之前,我们必须要先清楚虚拟 DOM
的概念,因为 Vue
中的 diff
算法比对的不是别人,正是虚拟 DOM
,那虚拟 DOM
到底是什么呢?(面试的时候可能会问)
虚拟 DOM
是用来表示真实 DOM
的 JS
对象,该对象并没有真实 DOM
那么多的属性,它有的只是个别用来描述真实 DOM
的属性,比如真实 DOM
的元素类型、其对应的子元素(也是虚拟 DOM
)等等,下面我们先来看一下 Vue
中的虚拟 DOM
到底长什么样?
<ul id='text-list'><li class="item1">你好呀</li><li class="item2">我是xxx</li><li class="item3">我们一起学习前端吧~</li>
</ul>
上面是我们再熟悉不过的一段 html
段落,看看它们被转为虚拟 DOM
之后会变成什么样吧~🤩
{ tagName: 'ul', props: { id: 'text-list'},children: [{tagName: 'li', props: { class: 'item1' }, children: ['你好呀']},{tagName: 'li', props: { class: 'item2' }, children: ['我是xxx']},{tagName: 'li', props: { class: 'item3' }, children: ['我们一起学习前端吧~']}]
}
果不其然,和我们说的一样,虚拟 DOM
就是一个 JS
对象,其上面的属性不多,都是用来描述真实 DOM
的结构的,比如说 tagName
是对应的元素名,props
是添加到元素上的属性,children
是由其子元素所组成的数组,每一个子元素也都是一个完整的虚拟 DOM
Diff 算法
介绍完了虚拟 DOM
之后,我们终于可以来讲讲什么是 diff
算法了,相信很多开发者都听过这个名词,但很多人会误认为 diff
算法是 React
或者 Vue
这种框架发明的,其实并不是,diff
很早就有了,它就是一种用来寻找两者差异的算法,那框架中的 diff
算法和传统的有什么区别呢?
1. 比较的目标不同
在框架中使用 diff
算法比对的目标是虚拟 DOM
,这是在前端框架中才有的概念,但是最终的目的都是一样的,就是为了找出两者的不同,所以也可以将 diff
算法理解成专门用来找不同的算法
2. 比较结果的用处不同
在其它应用场景中使用 diff
算法可能只是为了知道双方是否完全相同,但是在框架中,在虚拟 DOM
的基础上,diff
算法判定为类型相等的节点可以进行复用,判定为不同的节点则会删除重建,这也是 diff
算法最核心的思想—复用
3. 比较的方式不同
虚拟 DOM
从形状上来看就是一棵树, 新旧虚拟 DOM
进行比对的时候,每一层级的节点只会和同层级的节点进行比较,不会跨层级比较,但这样不就做不到完全复用了吗?其实这是出于性能考虑的最佳方案,如果旧虚拟 DOM
上面的一个节点要和新虚拟 DOM
的所有节点进行比较,虽然可以最大程度上的复用节点,但是同时也会因为比较次数过多而大量的消耗性能,为什么那么说呢?
假如旧树上有 n
个节点,每个节点都需要和新树上的 n
个节点进行比较,找到不同的节点之后还需要进行各种操作(替换、删除、增加,时间复杂度为 O(n)
),这样一来,新旧节点使用 diff
算法比较的时间复杂度就为 O(n^3)
,相当于三重 for
循环,这样太消耗性能了,于是框架对传统的 diff
算法进行了改造,选取了一种折中的方式——同级比较
如下图所示,只有在同一层的节点才会进行比较,而且只比较相同位置的节点,这样时间复杂度就降低到了 O(n)
。如果一个节点在比对的时候判定为不相同,并且它还有子节点,那该节点会被直接删除,而不深度遍历子节点进行比较
Snabbdom 源码分析
看到这个小标题你可能有点懵,这篇文章不应该是讲 diff
算法的吗?在介绍框架的 diff
算法之前,我们必须要知道 Vue2
并没有选择自己重新造一套 Virtual-DOM
的算法,而是在 Snabbdom
这个库的基础上构建了一个嵌入了框架本身的 fork
版本
所以说,Vue2
的 diff
算法就是在原有 Snabbdom
进行改造得到的,而 Vue3
的 diff
算法又是在 Vue2
的基础上改良的,React
的 diff
算法又和 Vue
大同小异,有异曲同工之妙。搞懂了 Snabbdom
的原理,框架中的 diff
算法自然也就理解了,虽然有部分逻辑不一致,但核心思想还是很相似的;还有一个原因就是 Snabbdom
不仅涵盖了 diff
算法的核心思想,而且由于源码并不涉及框架中的额外操作,所以阅读起来会简单很多
以上就是这篇文章为什么这篇文章不去专门解读 Vue
和 React
的源码,而是去研究 Snabbdom
源码的原因,这也对应了本篇文章的标题:不会 Vue
,但不影响大家学习 diff
算法,因为其不是一个具体的东西,它只是一种思想,不一定只在 Vue
或 React
这样的框架中才会应用到
h 函数
框架都有一个将开发者书写的代码转换为虚拟 DOM
的函数,比如在 React
中,jsx
语法是 React.createElement
的语法糖,虚拟 DOM
(在 React
中称为 react element
)就是该函数创建出来的,而 Snabbdom
创建虚拟 DOM
用的则是 h