一、前言
前段时间面试了几位同学,他们对虚拟DOM的回答常常强调虚拟DOM改进了性能,能提到关于重排和重绘的问题。然而虚拟DOM一定比直接操作真实DOM效率更高?除了解决重排和重绘的问题,虚拟DOM又有哪些优势呢?
二、举例
举个例子:假设有个列表,需要更新10项内容
1、直接操作真实DOM的情况:
遍历这个列表,并对每一项执行DOM操作
每次操作可能触发浏览器的重排(reflow)
和重绘(repaint)
如果这10次是同步操作的,浏览器可能会做出一些优化,但仍然会有比较大的开销,特别是在列表很大或者DOM结构足够复杂的情况下
2、通过虚拟DOM操作真实DOM的情况:
会计算更新后的虚拟DOM和当前虚拟DOM的差异(diffing)
将这些差异批量应用在真实DOM上,通常在一个渲染周期内完成
这种方式能够减少操作真实DOM次数,并减少重排(reflow)
和重绘(repaint)
3、性能比较:
如果只是简单的更新10次并没有复杂的界面或频繁的状态变化,直接操作真实DOM在渲染速度上可能会更快,因为避免了虚拟DOM的构建差异计算的开销。但如果这些操作导致频繁的重绘和重排,可能会影响帧率和用户交互的响应速度
在实践中虚拟DOM的性能优势并不总是立竿见影的,所以谈到虚拟DOM时不能仅仅只谈到性能问题
另: vue3相比vue2优化了diffing的算法,在diffing上的开销变小了
三、虚拟DOM的优势
虚拟DOM是提供真实DOM操作最小代价的中间商
1、批量更新和最小化DOM操作:减少重排、重绘
2、声明式渲染:让开发者专注于数据模型和如何映射到DOM的规则上,而不是操作DOM本身
3、组件化:描述了数据和真实DOM的关系,配合组件化开花,可以使用UI复用
4、跨平台:虚拟DOM作为一个中间层,可以不局限于浏览器,还可以渲染到其他平台。
总结起来,Vue使用虚拟DOM的原因主要是为了提高性能、简化开发和实现跨平台支持。通过虚拟DOM的机制,Vue可以减少实际DOM操作次数,提高应用的性能;简化开发者对DOM操作的关注,提高开发效率;实现跨平台支持,保持一致的开发体验。
四、vue生成真实DOM的过程
现在按以下步骤简单来实现下过程
1、 解析模板 :将模板字符串解析成一个抽象的语法树(AST)
2、 响应式系统:对组件的data对象进行响应式处理
3、 生成虚拟DOM:在首次渲染或数据更新时,渲染函数被调用,返回新的虚拟DOM树
4、 更新数据:当数据发生变化时,响应式系统会注意到这些变化,并触发重新渲染
5、 虚拟DOM Diffing
:将虚拟DOM和上次保存的虚拟DOM进行对比,找出差异
6、 打补丁(patch)
:计算差异应用到真实DOM上,有发生变化的部分会被更新
注:当前只考虑tagName和插值内容,不考虑属性和嵌套标签。
js
const regex = /<(.+?)\>{{(.+?)}}<\/\1>/;
// 编译模板生成AST
const compileTemplate = (template, data) => {
const match = template.match(new RegExp(regex, "gi")) || [];
return match.map((match) => {
const [, tag, children] = match.match(new RegExp(regex, "i"));
return {
tag,
children: data[children.trim()],
};
});
};
let _app = null;
let _template = "";
let _data = null;
let _vDOM = null;
// 响应式数据
const reactive = (data) => {
_data = new Proxy(data, {
get(obj, key) {
return Reflect.get(obj, key);
},
set(obj, key, value) {
const res = Reflect.set(obj, key, value);
update(key, value);
return res;
},
});
return _data;
};
// 渲染
const render = (app, template, data) => {
_vDOM = compileTemplate(template, data);
_app = app;
_template = template;
_data = data;
const fragment = document.createDocumentFragment();
_vDOM.forEach((vnode) => {
const { tag, children } = vnode;
const node = document.createElement(tag);
node.innerText = children;
fragment.appendChild(node);
});
app.innerHTML = ''
app.appendChild(fragment);
};
// 更新
const update = (value) => {
const newVDOM = compileTemplate(_template, _data);
_vDOM.forEach((vnode, index) => {
if (vnode.children !== newVDOM[index].children) {
patch(newVDOM[index].children, index);
}
});
_vDOM = newVDOM;
};
// 打补丁
const patch = (value, index) => {
const childNodes = _app.children;
if(childNodes[index]){
childNodes[index].innerText = value;
}
};
const template = `
<p>{{ title }}</p>
<p>{{ content }}</p>
`;
const data = reactive({
title: "虚拟DOM",
content: "vDOM",
});
render(document.getElementById("app"), template, data);
// 更改数据
data.title = "new VDOM"
data.content = "更新后的VDOM的内容"