一、React Dom Diff(协调算法)
React的DOM-Diff算法采用深度优先遍历的方式进行,为了提高性能,采用了以下优化策略。
1. 分层比较策略
React的Diff算法采用分层比较策略,只会对同一层级的节点进行比较。这种策略将算法复杂度从O(n³)优化到O(n),其中n是树中节点的数量。
2. 节点类型比较
-
类型不同:如果节点类型不同,React会直接销毁整个旧子树并构建新子树
-
类型相同:如果节点类型相同,React会比较两者的属性,然后递归比较子节点
3. 列表节点的Key优化
在处理列表时,React使用key属性来识别哪些元素是新增、移动或删除的:
-
没有key时,React会按顺序比较,可能导致性能问题
-
有key时,React可以准确识别节点身份,最小化DOM操作
4. 组件类型判断
-
相同组件类型:更新props并递归比较子节点
-
不同组件类型:卸载旧组件,挂载新组件
5. 递归子节点比较
React使用双指针算法优化子节点比较:
-
第一遍遍历:识别需要删除的节点
-
第二遍遍历:识别需要新增和移动的节点
6. 生成和应用补丁
最终,React收集所有差异并生成最小化的DOM操作指令,批量应用到真实DOM上,减少重排和重绘。
7、列表中的子元素:
在处理列表(如ul或ol)中的子元素时,React需要为每个子元素分配一个唯一的key属性。当列表发生变化时,React通过比较子元素的key值来确定哪些元素需要添加、删除或更新。为了获得最佳性能,key值应该是稳定且唯一的,例如数据的ID。
8、应用DOM-DIFF算法原理优化代码
尽量避免跨层级移动 ;
通过设置key优化元素移动时的性能 ;
尽量减少组件的嵌套层级,过深的层级会加大遍历深度,降低性能 ;
合理使用shouldComponentUpdate和PureComponent减少DIFF次数。
参考:
浅谈React中虚拟DOM、diff算法、fiber架构的关系(面试可用)
二、Vue DOM Diff
参考:
三、面试题
1、给出如下虚拟dom的数据结构,如何实现简单的虚拟dom,渲染到目标dom树
//样例数据
let demoNode = ({
tagName: 'ul',
props: {'class': 'list'},
children: [
({tagName: 'li', children: ['douyin']}),
({tagName: 'li', children: ['toutiao']})
]
});
复制代码
//构建一个render函数,将demoNode对象渲染为以下dom
<ul class="list">
<li>douyin</li>
<li>toutiao</li>
</ul>
复制代码
看到虚拟DOM,是不是感觉很玄乎,但是剥开它华丽的外衣,也就那样:
- 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;
- 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。 通过上面的介绍,下面,我们就来实现一个简单的虚拟DOM,并将其与真实的DOM关联。
构建虚拟DOM
虚拟DOM,其实就是用JavaScript对象来构建DOM树,如上ul组件模版,其树形结构如下:
通过JavaScript,我们可以很容易构建它,如下:
var elem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['item2']})
]
});
复制代码
note:Element为一个构造函数,返回一个Element对象。为了更清晰的呈现虚拟DOM结构,我们省略了new,而在Element中实现。
/*
* @Params:
* tagName(string)(requered)
* props(object)(optional)
* children(array)(optional)
* */
function Element({tagName, props, children}){
if(!(this instanceof Element)){
return new Element({tagName, props, children})
}
this.tagName = tagName;
this.props = props || {};
this.children = children || [];
}
复制代码
好了,通过Element我们可以任意地构建虚拟DOM树了。但是有个问题,虚拟的终归是虚拟的,我们得将其呈现到页面中,不然,没卵用。。
怎么呈现呢?
从上面得知,这是一颗树嘛,那我们就通过遍历,逐个节点地创建真实DOM节点:
1. createElement;
2. createTextNode.
怎么遍历呢?
因为这是一颗树嘛,对于树形结构无外乎两种遍历:
1. 深度优先遍历(DFS)
2. 广度优先遍历(BFS)
针对实际情况,我们得采用DFS,为什么呢?
因为我们得将子节点append到父节点中
好了,那我们采用DFS,就来实现一个render函数吧,如下:
Element.prototype.render = function(){
var el = document.createElement(this.tagName),
props = this.props,
propName,
propValue;
for(propName in props){
propValue = props[propName];
el.setAttribute(propName, propValue);
}
this.children.forEach(function(child){
var childEl = null;
if(child instanceof Element){
childEl = child.render();
}else{
childEl = document.createTextNode(child);
}
el.appendChild(childEl);
});
return el;
};
复制代码
此时,我们就可以轻松地将虚拟DOM呈现到指定真实DOM中啦。假设,我们将上诉ul虚拟DOM呈现到页面body中,如下:
var elem = Element({
tagName: 'ul',
props: {'class': 'list'},
children: [
Element({tagName: 'li', children: ['item1']}),
Element({tagName: 'li', children: ['item2']})
]
});
document.querySelector('body').appendChild(elem.render());
2、Virtual DOM比真实DOM快吗?
网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么?