Virtual DOM

要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实 dom 上会引起整个 dom 树的重绘和重排,有没有可能我们只更新我们修改的那一小块 dom 而不要更新整个 dom 呢?

diff算法能够帮助我们。

我们先根据真实 DOM 生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后VnodeoldVnode作对比,发现有不一样的地方就直接修改在真实的 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}

(温馨提示:VNodeoldVNode都是对象,一定要记住)

  • 用 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 节点后,节点上的classattributestyle以及绑定的事件
  • children 属性是vnode的子节点
  • text 属性是文本属性
  • elm 属性为这个vnode对应的真实 dom 节点
  • key 属性是vnode的标记,在diff过程中可以提高diff的效率

Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。