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 等过程。