了解浏览器加载html文件时需要做哪些事情(即浏览器的渲染过程),能够更好的帮助我们理解虚拟DOM。关于浏览器的渲染过程,可以参考我的另一篇博文https://blog.csdn.net/ty987654/article/details/78347390。浏览器的渲染过程大致可以分为5步:创建DOM树-----创建css规则树-----构建render树-----layout布局-----绘制render树。
为什么使用虚拟DOM?
当我们使用原生的js或jquery操作DOM时,浏览器会从构建DOM开始从头到尾执行一遍流程。
比如当在某一次操作时,需要更新10个DOM节点,理想状态下是一次性构建完成DOM树,然后再进行后续的DOM节点更新。但浏览器不会如此智能,当浏览器收到第1个更新DOM请求后,会立即执行该操作,但后续还有9次更新操作,所以浏览器会执行10次更新流程。如果浏览器在第1次更新时,计算出每个DOM节点的坐标值,接收第2次更新请求时,节点坐标值发生变化,那么第一次的计算相当于是无用功,白白浪费了资源与性能。
实现虚拟DOM:
虚拟DOM:使用js对象模拟DOM节点:页面的更新会反映在js对象上,操作js对象比实际上操作DOM的速度更快,更新完成后,将js对象映射成真实的DOM,然后由浏览器进行渲染。
真实的DOM节点:
<div id="real-container">
<ul>
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
</div>
使用js对象模拟DOM节点:页面的更新会反映在js对象上,操作js对象比实际上操作DOM的速度更快,更新完成后,将js对象映射成真实的DOM,然后由浏览器进行渲染。
element = {
tagName: 'div',
props: { id: '' },
children: [
{
tagName: 'ul',
props: {},
children: [
{
tagName: 'li',
props: {class: 'item'},
children: [Item1]
},
{
tagName: 'li',
props: {class: 'item'},
children: [Item2]
},
{
tagName: 'li',
props: {class: 'item'},
children: [Item3]
},
]
}
]
}
const tree = Element('div', { id: 'virtual-container' }, [
Element('ul', {}, [
Element('li', { class: 'item' }, ['Item 1']),
Element('li', { class: 'item' }, ['Item 2']),
Element('li', { class: 'item' }, ['Item 3']),
]),
]);
const root = tree.render();
document.getElementById('virtualDom').appendChild(root);
Element方法的实现:
function Element(tagName, props, children) {
if (!(this instanceof Element)) {
return new Element(tagName, props, children);
}
this.tagName = tagName;
this.props = props || {};
this.children = children || [];
this.key = props ? props.key : undefined;
let count = 0;
this.children.forEach((child) => {
if (child instanceof Element) {
count += child.count;
}
count++;
});
this.count = count;
}
// tagName表示节点名,如div,props表示节点的属性,如class,children表示子节点
js对象映射成真实的DOM:
Element.prototype.render = function() {
// 通过createElement创建元素
const el = document.createElement(this.tagName);
const props = this.props;
// 将属性添加在元素上
for (const propName in props) {
setAttr(el, propName, props[propName]);
}
// 存在子元素递归调用创建子元素
this.children.forEach((child) => {
const childEl = (child instanceof Element) ? child.render() : document.createTextNode(child);
// 通过appendChild方法添加子元素
el.appendChild(childEl);
});
return el;
};
diff算法:
比较两棵树的差异,采用diff算法。两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比,这样算法复杂度就会变成O(n)
虚拟DOM的核心可以总结为:
1、用js对象结构表示一个DOM树,然后基于对象树创建一个真正的DOM树,将DOM树插入到文档中
2、当状态变更的时候,重新构造一个新的对象树,将新的对象树与旧的对象树进行比较,记录它们的不同之处
3、将记录的不同之处应用在真正的DOM树上,实现视图的更新
虚拟DOM与真实DOM的区别:
1. 虚拟DOM不会进行回流与重绘操作。
2. 真实DOM频繁进行回流与重绘操作效率是非常低的。