前言
在jQuery诞生之前,当我们需要更新页面的时候,都是通过document.getElementById()、document.getElementByClassName()等等最原生的方法来获取DOM结构,然后再用一些比如innerHTML()函数来改变视图。
由于这种操作最原生的方法来操作DOM结构效率极低,因此诞生了jQuery。有了jQuery之后,给开发者带来了极大的便利性,操作DOM变得不再那么困难,只需使用非常简单的$符号即可获取相应的DOM节点。因此,jQuery出现之后,凭借其选择器方法等特性迅速风靡,成为几乎所有网站开发必备的JavaScript库,乃至开创了jQuery编程风格。
然而,2018年7月25日,GitHub官方宣布其官网的前端部分已经彻底删除了jQuery。这些事件基本标志着jQuery时代的结束。目前,在新开发的web项目中,已经基本不再使用jQuery框架,而是开始使用MVVM框架。
jQuery时代的结束,意味着通过直接操作DOM结构来更新视图的时代结束,同时标志着通过状态来更新视图的时代开始。因此诞生了诸如React、Vue等MVVM框架。
React Diff算法与传统Diff算法的区别
传统Diff算法
通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,其中n是树中节点的个数。尽管这个复杂度很高,但是确实一个好的算法,只是在实际前端渲染的场景中,随着DOM节点的增多,性能开销也会非常大。
React的Diff算法
React提出了一种虚拟DOM的概念,即在所有React组件中的DOM结构不是真正的DOM结构,而是虚拟的,当在浏览器运行的时候,这些虚拟的DOM结构才变为真实的DOM结构。
React在传统Diff算法的基础之上,针对前端渲染的具体情况进行了具体分析,做出了相应的优化,从而实现了一个稳定高效的diff算法。
React Diff算法是将Virtual DOM树转换成真实DOM树的最少操作的过程,它用三种策略将传统Diff算法的复杂度从O(n^3) 降到了O(n)的复杂度。
React Diff算法的三种策略
策略1:虚拟DOM树分层比较
DOM节点跨层级的移动操作发生频率很低,可以忽略不计。
策略2:组件间的比较
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
策略3:元素间的比较
对于同一层级的一组子节点,通过唯一id进行区分。
React Diff算法三种策略实现原理
下面重点讲解以上三种策略的具体实现:
策略1的实现原理及过程
虚拟DOM树分层比较主要是指React只会对相同颜色方框内的DOM节点进行比较,忽略DOM节点跨层级的移动操作。即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。由此一来,最直接的提升就是复杂度变为线型增长而不是原先的指数增长。
react diff算法之层级控制示意图.png
另外,如果真的发生跨层级移动(如下图),例如某个DOM及其子节点进行移动挂到另一个DOM下时,React是不会判断出子树仅仅是发生了移动,而是会直接销毁,并重新创建这个子树,然后再挂在到目标DOM上。从这里可以看出,在实现自己的组件时,保持稳定的DOM结构会有助于性能的提升。事实上,React官方也是建议不要做跨层级的操作。因此在实际使用中,比方说,我们会通过CSS隐藏或显示某些节点,而不是真的移除或添加DOM节点。其实一旦接受了React的写法,就会发现前面所说的那种移动的写法几乎不会被考虑,这里可以说是React限制了某些写法,不过遵守这些实践确实会使得React有更好的渲染性能。
react diff算法之跨层级移动.png
策略2的实现原理及过程
a、如果是同类型组件,则按原策略(层级比较)继续比较Virtual DOM树即可。
b、同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以 用户 可以通过 shouldComponentUpdate() 来判断是否需要 判断计算。
c、不同类型的组件,将一个(将被改变的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。
策略3的实现原理及过程
元素间的比较是diff算法最核心的部分。下面举个例子,是一个涉及到新集合中有新加入的节点且老集合存在需要删除的节点的情形。代码如下图所示。
diff算法元素间的比较.png
通过点击来控制文字和数字的显示与消失。这种JSX可以说是太常用了。
元素间的比较,主要包含3中操作:删除、插入、移动。
diff算法元素间的移动.png
如上图所示,组件D已经在集合(A,B,C,D)里了,且集合更新时,D没有发生更新,只是位置改变,如新集合(A,D,B,C),D在第二个,无须像传统diff,让旧集合的第二个B和新集合的第二个D 比较,并且删除第二个位置的B,再在第二个位置插入D,而是 (对同一层级的同组子节点) 添加唯一key进行区分,移动即可。
还有新集合中有新加入的节点,旧集合中有删除的节点等情形。
总结
搞清楚diff算法对于我们理解react是如何进行高效的dom结构渲染是很有帮助的,能够帮助我们更好的理解react底层的一些实现原理。
我们在学习一些框架的时候,不仅要只学会如何使用这些框架,更应该学会它背后的实现逻辑,这样才会有本质的提升。
网友评论