虚拟DOM
虚拟dom是相对于浏览器所渲染出来的真实dom的。
在react,vue等技术出现之前,要改变页面展示内容只能通过遍历查询dom树的方式找到需要修改的dom, 然后修改样式行为或者结构,来达到更新ui的目的。
这种方式相当消耗计算资源,因为每次查询dom几乎都需要遍历整棵dom树,如果建立一个与dom树对应的虚拟dom对象( js对象),以对象嵌套的方式来表示dom树,那么每次dom的更改就变成了js对象的属性的更改,查找js对象的属性变化要比查询dom树的性能开销小。
1. 什么是虚拟DOM
实际上是一层对真实DOM的抽象,以JavaScript对象 (VNode节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。
在Javascript对象中,虚拟DOM表现为一个Object对象。并且最少包含标签名(tag)、属性(attrs)和子元素对象(children)三个属性。
创建虚拟DOM是为了将虚拟节点渲染到页面视图中,所以虚拟DOM对象节点与真实DOM属性一一照应。
定义真实DOM:
<div id="app">
<p class="p">节点内容</p>
<h3>{{ foo }}</h3>
</div>
实例化vue:
const app = new Vue({
el:"#app",
data:{
foo:"foo"
}
})
观察render
的render
,能得到虚拟DOM
(function anonymous() {
with(this){
return _c('div', {
attrs:{
"id":"app"}
},[_c('p', {
staticClass:"p"
},[_v("节点内容")]), _v(" "), _c('h3', [_v(_s(foo))])])
}
})
通过VNode
,vue
可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff
算法得出一些需要修改的最小单位,再更新视图,减少了dom
操作,提高了性能
2. 为什么需要虚拟DOM
DOM
是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM
操作引起的
真实的DOM
节点,哪怕一个最简单的div
也包含着很多属性,可以打印出来直观感受一下:
由此可见,操作DOM
的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验
举个例子:
用传统的原生api
或jQuery
去操作DOM
时,浏览器会从构建DOM
树开始从头到尾执行一遍流程
当在一次操作时,需要更新10个DOM
节点,浏览器没这么智能,收到第一个更新DOM
请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程
而通过VNode
,同样更新10个DOM
节点,虚拟DOM
不会立即操作DOM
,而是将这10次更新的diff
内容保存到本地的一个js
对象中,最终将这个js
对象一次性attach
到DOM
树上,避免大量的无谓计算
很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这是一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是小程序,也可以是各种GUI
3. diff 算法
3.1. 概念
diff算法是一种优化手段,将前后两个模块进行差异化对比,修补差异的过程叫做patch(打补丁)
为什么 vue,react 这些框架中都会有 diff 算法呢,真实 dom 的开销是很大的,这个跟性能优化中的重绘意义类似。某些时候修改了页面中的某个数据,如果直接渲染到真实 DOM 中会引起整棵树的重绘,那么能不能只让改变过的数据映射到真实 DOM,做一个最少的重绘呢,这就是 diff 算法要解决的事情。
3.2. virtual DOM和真实DOM的区别
virtual DOM是将真实DOM的数据抽取出来,以对象的形式模拟,diff算法比较的也是virtual DOM
代码理解:
<div>
<p>Hello World</p>
</div>// 转换成虚拟节点 类似于下面这种
const Vnode = {
tag: 'div',
children: [
{tag:'p', text:'Hello World'}
]
}
3.3. diff是如何比较的
就是对操作前后的dom树同一层的节点进行对比,一层一层对比,然后再插入真实的dom中,重新渲染。
vue中列表循环需加 :key="唯一标识" 唯一标识可以是 item 里面 id index等,因为 vue 组件高度复用增加 Key 可以标识组件的唯一性,那么 key 是如何更高效的更新虚拟 DOM 的呢
希望可以在 B 和 C 之间加一个 F,diff 算法默认执行起来是这样的:即把 C 更新成 F,D 更新成C ,E 更新成 D,最后再插入 E,是不是很没有效率?
所以需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点。