Virtual DOM
要知道渲染真实DOM
的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实 dom 上会引起整个 dom 树的重绘和重排,有没有可能我们只更新我们修改的那一小块 dom 而不要更新整个 dom 呢?
diff算法
能够帮助我们。
我们先根据真实 DOM 生成一颗virtual DOM
,当virtual DOM
某个节点的数据改变后会生成一个新的Vnode
,然后Vnode
和oldVnode
作对比,发现有不一样的地方就直接修改在真实的 DOM 上,然后使oldVnode
的值为Vnode
。
diff
的过程就是调用名为patch
的函数,比较新旧节点,一边比较一边给真实的 DOM打补丁。
virtual DOM 和真实 DOM 的区别
virtual DOM
是将真实的 DOM 的数据抽取出来,以对象的形式模拟树形结构。比如 dom 是这样的:
1<div>
2 <p>123</p>
3</div>
对应的 virtual DOM(伪代码):
1const Vnode = {
2 tag: 'div',
3 children: [{ tag: 'p', text: '123' }],
4}
(温馨提示:VNode
和oldVNode
都是对象,一定要记住)
-
用 JavaScript 对象模拟 DOM
-
把此虚拟 DOM 转成真实 DOM 并插入页面中
-
如果有事件发生修改了虚拟 DOM
-
比较两棵虚拟 DOM 树的差异,得到差异对象
-
把差异对象应用到真正的 DOM 树上
VNode
对于 VNode
,相信大家一点都不陌生,用于表示虚拟节点,是实现Virtual DOM
的一种方式。那么它究竟是怎样的呢?我们就去 Vue 源码里探讨一下。
1export default class VNode {
2 tag: string | void;
3 data: VNodeData | void;
4 children: ?Array<VNode>;
5 text: string | void;
6 elm: Node | void;
7 ns: string | void;
8 context: Component | void; // rendered in this component's scope
9 key: string | number | void;
10 componentOptions: VNodeComponentOptions | void;
11 componentInstance: Component | void; // component instance
12 parent: VNode | void; // component placeholder node
13
14 // strictly internal
15 raw: boolean; // contains raw HTML? (server only)
16 isStatic: boolean; // hoisted static node
17 isRootInsert: boolean; // necessary for enter transition check
18 isComment: boolean; // empty comment placeholder?
19 isCloned: boolean; // is a cloned node?
20 isOnce: boolean; // is a v-once node?
21 asyncFactory: Function | void; // async component factory function
22 asyncMeta: Object | void;
23 isAsyncPlaceholder: boolean;
24 ssrContext: Object | void;
25 fnContext: Component | void; // real context vm for functional nodes
26 fnOptions: ?ComponentOptions; // for SSR caching
27 fnScopeId: ?string; // functional scope id support
28
29 constructor(
30 tag?: string,
31 data?: VNodeData,
32 children?: ?Array<VNode>,
33 text?: string,
34 elm?: Node,
35 context?: Component,
36 componentOptions?: VNodeComponentOptions,
37 asyncFactory?: Function
38 ) {
39 this.tag = tag;
40 this.data = data;
41 this.children = children;
42 this.text = text;
43 this.elm = elm;
44 this.ns = undefined;
45 this.context = context;
46 this.fnContext = undefined;
47 this.fnOptions = undefined;
48 this.fnScopeId = undefined;
49 this.key = data && data.key;
50 this.componentOptions = componentOptions;
51 this.componentInstance = undefined;
52 this.parent = undefined;
53 this.raw = false;
54 this.isStatic = false;
55 this.isRootInsert = true;
56 this.isComment = false;
57 this.isCloned = false;
58 this.isOnce = false;
59 this.asyncFactory = asyncFactory;
60 this.asyncMeta = undefined;
61 this.isAsyncPlaceholder = false;
62 }
63
64 // DEPRECATED: alias for componentInstance for backwards compat.
65 /* istanbul ignore next */
66 get child(): Component | void {
67 return this.componentInstance;
68 }
69}
这里千万不要因为 VNode
的这么属性而被吓到,或者咬紧牙去摸清楚每个属性的意义,其实,我们主要了解其几个核心的关键属性就差不多了,例如:
tag
属性即这个vnode
的标签属性
data
属性包含了最后渲染成真实 dom 节点后,节点上的class
,attribute
,style
以及绑定的事件
children
属性是vnode
的子节点
text
属性是文本属性
elm
属性为这个vnode
对应的真实 dom 节点
key
属性是vnode
的标记,在diff
过程中可以提高diff
的效率
Virtual DOM
除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode
的 create、diff、patch 等过程。