虚拟DOM和DOM diff
虚拟DOM
虚拟DOM概念
是一个能代表DOM树的 JavaScript 对象,包含了 tag、props、children 三个属性。
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应
<div id="app">
<p class="text">hello world!!!</p>
</div>
上面的 HTML 转换为虚拟 DOM 如下:
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}
该对象就是我们常说的虚拟 DOM 了,因为 DOM 是树形结构,所以使用 JavaScript 对象就能很简单的表示。
虚拟DOM优点
-
减少操作
将多次操作合并为一个操作,如添加1000个节点不必手动一次一次添加,可以一次性完成。(减少次数)
DOM diff更加灵活以操作。 如已有90个节点,想再添加10个节点,简单的JS无法判断新增,只能将其合并为100个进行添加。但diff可以判断10个新增节点,这样可以省却多余的操作。(减少范围)
-
跨平台
虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚 拟 DOM 本质上只是一个 JS 对象
虚拟DOM缺点
需要额外的创建函数,如 createElement 或 h,但可以通过 JSX 来简化成 XML 写法。当数据繁杂时,虚拟DOM还是会崩溃,原生DOM则不会。以节点为例,增设十万个节点的时候虚拟DOM常常会崩溃。
DOM diff
DOM diff概念
diff其实就是different缩写!!!
diff 算法,顾名思义,就是比对新老 VDOM 的变化,然后将变化的部分更新到视图上。
该改变原理如下:
我们将其想象成树状图:
当我们准备删掉带有“hello”的这条的时候,会变成如下情况:
这个过程的原理在于,diff会先将 “hello” 改变成 "world",然后将带有 “world”的这条span删除。 先后经历了两个步骤。
DOM diff 可能的样式
[
{type: ‘INSERT’, vNode: ... },//插入节点
{type: ‘TEXT’, vNode: ... },//更新文本
{type: ‘PROPS’, propsPatch: [...]}//更新属性
]
显而易见,dom diff可以减少我们很多操作,其优点就是虚拟dom的优点。
DOM diff的逻辑
Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新
- 如果节点是组件就看 Component diff
- 如果节点是标签就看 Element diff
Component diff
- 如果节点是组件,就先看组件类型
- 类型不同直接替换(删除旧的)
- 类型相同则只更新属性
- 然后深入组件做 Tree diff(递归)
Element diff
- 如果节点是原生标签,则看标签名
- 标签名不同直接替换,相同则只更新属性
- 然后进入标签后代做 Tree diff(递归)
DOM diff 问题
DOM diff的逻辑虽然使用起来很方便,但是有个硬伤:
在没有表明key属性的情况下,你无法删除你想要删除的那个它!
例子:
首先,在三个 input 里依次输入三角形、正方形和圆形。(这里没有布置key属性)
当我们删除正方形这一栏
很奇怪,尽管2被删除了,但正方形还是正方形。为什么呢?
就像前面所说的原理,diff会先把 正方形的2 这个属性改成 3,然后再删除 圆形 这一栏。
想真正删除正方形这一栏,必须加上key。
我们先将数组{1,2,3}加上属性id,这里的id就是我们作为key的标识,即:
{ id: 1 }, { id: 2 }, {id: 3 }
图像就变成这样:
接着我们删除正方形这一栏:
这时候就成功了,这时正方形就不会出现了。
值得注意的是:
**我们不能将index作为key。**因为index具有连续性的,假设0,1,2中,我们删掉1,那么顺序就会变成0,1,并不会变成1,2.这样就会陷入我们之前所说的你无法删除你想要的那个他了。