Appearance
虚拟DOM
DOM的工作原理
- 我们写的代码是js代码,但是浏览器引擎是使用C++写的。
- 通过 WebIDL(Web Interface Definition Language) Web接口定义语言,这个就是定义浏览器和JS之间是如何进行通信的。
- 通过 WebIDL 浏览器开发者可以描述哪些类和方法能够被JS访问,以及这些方法应该如何映射到JS中的对象和方法。
- 工作流程:
- 首先JS引擎执行JS代码,如果JS引擎发现是要创建DOM节点,会将其识别成一个API来调用,然后向浏览器底层(渲染引擎)发出请求,由浏览器底层(渲染引擎)负责创建这个DOM元素。浏览器底层创建完DOM元素之后,需要向调用端返回一个结果。
本质
- 最初虚拟 DOM 是由 React 团队提出的: 虚拟DOM是一种编程概念。
- 在这个概念里, UI以一种理想化,或者说'虚拟的'表现形式被保存在内存中。
- 理论上讲,无论使用什么样的结构,只要能将文档结构展示出来,那么这种结构就是一种虚拟DOM,但实际上也就只有JS对象适合干这种事情。
- 在Vue中可以调用一个名叫h的函数,该函数返回的就是虚拟DOM。
示例:
const vnode = h('div', { class: 'bar', innerHTML: 'hello' })
结果:
{
tag: "div",
data: {
class: "bar",
domProps: {
innerHTML: "hello"
}
},
children: undefined,
text: undefined,
elm: undefined,
key: undefined,
componentOptions: undefined,
context: VueComponentInstance,
isComment: false,
isStatic: false
}- 由此可以看出虚拟DOM的本质就是一个普通的JS对象。
为什么要使用虚拟DOM
在最早期的时候,前端用过手动操作DOM节点来编写代码。
// 创建一个新的<div>元素
var newDiv = document.createElement("div");
// 给这个新的<div>添加一些文本内容
var newContent = document.createTextNode("Hello, World!");
// 把文本内容添加到<div>中
newDiv.appendChild(newContent);
// 最后,把这个新的<div>添加到body中
document.body.appendChild(newDiv);
// 假设我们有一个已存在的元素ID为'myElement'
var existingElement = document.getElementById("myElement");
// 更新文本内容
existingElement.textContent = "Updated content here!";
// 更新属性,例如改变样式
existingElement.style.color = "red";
// 假设我们要删除ID为'myElement'的元素
var elementToRemove = document.getElementById("myElement");
// 获取父节点
var parent = elementToRemove.parentNode;
// 从父节点中移除这个元素
parent.removeChild(elementToRemove);
// 创建新节点
var newNode = document.createElement("div");
newNode.textContent = "这是新的文本内容";
// 假设我们想把这个新节点插入到id为'myElement'的元素前面
var referenceNode = document.getElementById("myElement");
referenceNode.parentNode.insertBefore(newNode, referenceNode);上面的代码从编程范式的角度看属于命令式编程,这种编程方式的性能一定是最高的。
所以创建一个div的DOM节点,没有哪种方式是比 document.createElement("div") 这句代码还要高的。
虽然这种性能是最高的,但是在实际开发中,往往会倾向于更加方便的方式。
比如使用innerHTML的方式。
var app = document.getElementById("app");
app.innerHTML += `
<div class="message">
<div class="info">
<span>张三</span>
</div>
<div class="btn">
<a href="#" class="removeBtn" _id="1">删除</a>
</div>
</div>`;虽然这种方式在性能上差一些,但是写起来会轻松一些。
原因就是使用innerHTML涉及到了两个层面的计算:
- 解析字符串(JS层面)
- 创建对应的DOM节点(DOM层面)
使用虚拟DOM也涉及到两个层面的计算:
- 创建JS对象(虚拟DOM,JS层面)
- 根据JS对象创建对应的DOM节点(DOM层面)
JS层面的计算和DOM层面的计算是完全不同的。
实际上无论使用innerHTML还是使用虚拟DOM,在初始化时性能相差无几。虚拟DOM的快实际上是在更新的时候。
更新:
使用innerHTML时涉及的计算层面:
- 销毁所有旧的DOM(DOM层面)
- 解析模板字符串(JS层面)
- 重新创建所有的DOM节点(DOM层面)
如果使用虚拟DOM,只会涉及到两个层面的计算:
- 使用diff计算出更新的节点(JS层面)
- 更新必要的节点(DOM层面)
所以常说的虚拟DOM快 是有前提的
- 首先看和谁比较
- 如果和原生JS操作DOM相比,那无疑肯定是原生JS操作性能高,因为虚拟DOM多了一层计算。
- 如果和innerHTML相比,初始化时两者差距并不大,但是在更新时虚拟DOM会比innerHTML性能更高。
总结一句话:
- 使用虚拟DOM是为了防止组件在重渲染时导致的性能恶化。