Virtual Dom

什么是DOM

DOM就是文档对象模型,什么是文档对象模型?这就需要好好说说了。

HTML的文档document页面是一切的基础,没有它dom就无从谈起。

当创建好一个页面并加载到浏览器时,DOM就悄然而生,它会把网页文档转换为一个文档对象,主要功能是处理网页内容。

在这个文档对象里,所有的元素呈现出一种层次结构,就是说除了顶级元素html外,其他所有元素都被包含在另外的元素中。

假如有这么一段html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>

<head>

<title>文档标题</title>

</head>

<body>

<a href="">我的链接</a>

<h1>我的标题</h1>

</body>

</html>

那么它的树就应该是下面这样的一颗倒长的树。

一颗家谱树,而家谱树本身就是一种模型,其典型用法是表示表示人类家族谱系。

它很容易表明家族成员之间的关系,把复杂的关系简明地表示出来,因此这种模型非常适合表示一份html的文档:

imgimg

文档对象模型就是基于这样的文档视图结构的一种模型所有的html页面都逃不开这个模型,也可以把它称为节点树更为准确。

什么是虚拟DOM

再来复习一下,什么是虚拟DOM。

虚拟DOM的定义是用一个javascript对象通过对象属性的方式去描述一个dom节点。

举个例子:

我们现在有一个a标签,如果用普通的写法是这样子的:

1
<a href="http://xxx">链接</a>

将a标签通过js对象来描述的话,是这样的:

1
{  tag: "a",  attrs: {    href: "http://xxx"  },  text: "链接",  children: []}

一个真实的dom节点就这样通过js对象描述出来了。

为什么需要虚拟DOM?

我们可以看下a标签在浏览器下的输出,仅仅是一个空的a标签,就存在300+个属性。浏览器上面的真实DOM,除了一些基本的属性以外,还有很多为了支持标签本身特性而存在的很多属性和方法,这些属性和方法都是在浏览器底层去使用的,而对js来说真正有用的属性可能不到10个。

因此,前端很多时候对于dom操作都是极力避免的,如果确实需要,则希望将操作次数减到最少。由此产生了虚拟DOM,利用js去描述一个dom节点,只保留一些必要的、足够表达这个节点的属性。更新js对象比直接更新dom节点要节省很多的时间。

我们可以用js来模拟dom节点的更新,每当dom更新之后,我们先比对虚拟dom,找出需要更新的地方,然后最后再统一更新真实dom。

为什么DOM更新慢?

更新DOM并不慢,就像更新任何JavaScript对象一样。

那究竟是什么让更新真正的DOM变慢?

是绘制。

布局过程中,绘制占用了大部分时间。

结合下图,以及此文章,你会明白,更新 DOM 的真正问题是屏幕的绘制。

img

负责在浏览器屏幕上显示或呈现网页的渲染引擎解析HTML页面以创建DOM。它还解析CSS并将CSS应用于HTML,从而创建渲染树,此过程称为**attachment**。

所以,当我们这样做时

1
document.getElementById('elementId').innerHTML="New Value"

发生以下事情:

  1. 浏览器必须解析HTML
  2. 它删除了elementId 的子元素
  3. 使用"New Value"更新DOM
  4. 重新计算父和子的CSS
  5. 更新布局,即每个元素在屏幕上的精确坐标
  6. 遍历渲染树并在浏览器显示上绘制它

重新计算CSS和更改布局使用复杂的算法,它们会影响性能。

因此,更新真正的DOM并不仅仅涉及更新DOM,而是涉及许多其他过程。

此外,上述每个步骤都针对真实DOM的每次更新运行,即如果我们更新真实DOM 10次,则上述步骤中的每一个将重复10次。这就是为什么更新 DOM 很慢的原因。

Vue中的Virtual DOM

定义

我们打开vue源码文件的VNode类,这个类就是Vue用来描述真实dom节点的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
export default class VNode {
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}

get child (): Component | void {
return this.componentInstance
}
}

可以看到上面有一些常用的属性:tag表示标签名,text表示节点的文本内容,elm表示虚拟节点对应的真实dom节点等等。

分类

vue通过VNode可以描述6种节点类型:

  • 注释节点
  • 文本节点
  • 克隆节点
  • 元素节点
  • 组件节点
  • 函数式组件节点

diff算法

有兴趣的可以参考这篇文章:https://mp.weixin.qq.com/s/V2PLADamTyE4krSNuG6leQ

参考文章:

什么是DOM

从Vue角度理解虚拟DOM

重新认识虚拟DOM