虚拟DOM

https://www.jianshu.com/p/af0b398602bc

虚拟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"
    }
})

观察renderrender,能得到虚拟DOM

(function anonymous() {
    with(this){
        return _c('div', {
            attrs:{
                "id":"app"}
        },[_c('p', {
            staticClass:"p"
        },[_v("节点内容")]), _v(" "), _c('h3', [_v(_s(foo))])])
    }
})

通过VNodevue可以对这颗抽象树进行创建节点,删除节点以及修改节点的操作, 经过diff算法得出一些需要修改的最小单位,再更新视图,减少了dom操作,提高了性能

2. 为什么需要虚拟DOM

DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的

真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下: 由此可见,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验

举个例子:

用传统的原生apijQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程

当在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程

而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attachDOM树上,避免大量的无谓计算

很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这是一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是小程序,也可以是各种GUI

3. diff 算法

参考:https://www.infoq.cn/article/uDLCPKH4iQb0cR5wGY7f

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 算法就可以正确的识别此节点,找到正确的位置区插入新的节点。