本文主要包含以下内容:
一、 虚拟DOM
- 虚拟DOM是什么
- 虚拟DOM的优点
- 虚拟DOM的缺点
二、 DOM diff
- DOM diff是什么
- DOM diff的大致逻辑
- DOM diff如何进行比较
- 解决DOM diff存在的问题
一、虚拟DOM
1. 虚拟DOM是什么?
1.1 DOM回顾
MDN对其解释如下:
文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。 事实上为了让网页实现各种各样的效果,浏览器给window添加了一个document对象,我们将网页抽象成一个document对象,通过这个document对象操作整个页面的所有的节点。上述模型
Document Object Model也就是DOM。
我们每一次使用 js 操作 DOM,或者 DOM 每一次细微的改变,都会引发页面的重绘(paint)和重排(layout),这也是我们经常所说的“DOM操作慢”的根本原因。
1.2 虚拟DOM
虚拟DOM,也叫VDOM,一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素以及其他属性。其本质是一个JS对象.它是仅存于内存中的DOM,因为还未展示到页面中,所以称为VDOM。
举个随处可见的例子:
var a = document.createElement("div"); //这就是VDOM
那么如何让VDOM变成真实的DOM呢?
-简单,只需将节点append到页面中:
var a = document.createElement("div");
document.body.append(a); // 此时body内就会创建一个div标签,真实DOM
-
Vue、React中的虚拟DOM
- Vue
h('div', { class: 'red', on: { click: () => {} }, }, [h('span', {}, 'span1'), h('span', {}, 'span2')])- React
createElement('div', {className:'red', onClick: () => {}}, [ createElement('span', {}, 'span1'), createElement('span', {}, 'span2') ] )
2.虚拟DOM的优点
2.1 减少DOM操作
-
减少DOM操作的次数,虚拟DOM可以将多次操作合并为一次操作,例如在div中添加1000个甚至更多的节点,常规的方法是一个接一个的操作的。
-
减少DOM操作的范围,虚拟DOM借助
DOM diff(后文会说)可以把多余的操作去掉,例如添加1000个节点,实际上只有10个节点是新增的,DOM diff会将其简化操作,而不是重绘。
2.2 能够跨平台渲染
虚拟DOM不仅能够变成DOM,还可以变成小程序、ios应用、安卓应用,因为其本质是一个JS对象。
3. 虚拟DOM的缺点
需要通过额外的创建函数,如React需要creatElement,Vue 需要h函数等,但是可以通过JSX语法(React)、Vue-loader(Vue)来解决。如下:
**Vue Template**
通过`Vue-loader`转化为`h`形式
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
-----------------------------------------------
**React JSX**
通过`bable`转化`creatElemet`形式
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
二、DOM diff
1. 什么是DOM diff
DOM diff两个虚拟DOM树对比的算法,diff 算法仅在两个树的同级的虚拟节点之间做比较,递归地进行比较,最终实现整个 DOM 树的更新。
DOM diff本质上就是一个函数,我们称之为patch函数。有两个参数,分别是旧的虚拟DOM和新的虚拟DOM
patches=patch(oldVnode,newVnode)
patches就是要运行的DOM操作,类似于:
[
{type: 'INSERT', vNode: ... },
{type: 'TEXT', vNode: ... },
{type: 'PROPS', propsPatch: [...]}
]
2. DOM diff的大致逻辑
-
tree diff(层级比较)
1.将新旧两棵树逐层对比,找出哪些节点需要更新。
2.如果节点是组件就看
Component diff3.如果节点是标签就看
Element diff -
Component diff
1.如果节点是组件,就先看组件类型,类型不同直接替换(删除旧的),类型相同则更新属性。
2.然后深入组件做
tree diff -
Element diff
1.如果节点是原生标签,则看标签名,标签名不同直接替换,相同则更新属性。
2.然后进入标签后代做
tree diff
3.DOM diff如何进行比较
如下代码:
<div :class="x">
<span v-if="y">{string1}</span>
<span>{string2}</span>
</div>
以上代码树的结构图可用下图表示:
我们默认所有节点都是可变化的,假定div中的class变成了red,于是patch函数就会开始遍历并进行比较。
下面我们再变化,将第一个span删除。
计算机会认为是第一个span更新了,第二个span删除了!这就是DOM diff所存在的问题。
4. 解决DOM diff存在的问题
那么有什么办法可以解决DOM diff存在的问题呢?答案就是使用key(react跟vue都需要使用单独的key,这个key在同属于一个父节点的各兄弟节点上需要不同。)
如下:
//React JSX语法
<div>
{[{id:1,value:1},{id:2,value:2},{id:3,value:3}].map((v)=>{
return <span key={v.id}/>
})}
</div>