Vue源码探秘(Virtual DOM)
Opened this issue · 0 comments
引言
Virtual DOM
(后文简称vdom
)的概念大规模的推广得益于react
的出现,vdom
也是react
框架比较重要的特性之一。相比较频繁的手动去操作dom
而带来性能问题,vdom
很好的将dom
做了一层映射关系,进而将在我们本需要直接进行dom
的一系列操作,映射到了操作vdom
。
Vue.js 2.0
引入vdom
,比Vue.js 1.0
的初始渲染速度提升了 2-4 倍,并大大降低了内存消耗。那么,什么是vdom
呢?
让我们进入今天的文章。
VNode
VNode
是 Virtual DOM
在 Vue.js
中的数据结构定义,它被定义在 src/core/vdom/vnode.js
中:
// src/core/vdom/vnode.js
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
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;
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child(): Component | void {
return this.componentInstance;
}
}
可以看到 VNode
是一个类,有很多属性。每一个vnode
都映射到一个真实的dom
节点上。我们这里先了解几个重要的属性:
tag
: 对应真实节点的标签名data
: 当前节点的相关数据(节点上的class
,attribute
,style
以及绑定的事件),是VNodeData
类型。该类型声明在flow/vnode.js
中:
// flow/vnode.js
declare interface VNodeData {
key?: string | number;
slot?: string;
ref?: string;
is?: string;
pre?: boolean;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: { [key: string]: any };
style?: string | Array<Object> | Object;
normalizedStyle?: Object;
props?: { [key: string]: any };
attrs?: { [key: string]: string };
domProps?: { [key: string]: any };
hook?: { [key: string]: Function };
on?: ?{ [key: string]: Function | Array<Function> };
nativeOn?: { [key: string]: Function | Array<Function> };
transition?: Object;
show?: boolean; // marker for v-show
inlineTemplate?: {
render: Function,
staticRenderFns: Array<Function>
};
directives?: Array<VNodeDirective>;
keepAlive?: boolean;
scopedSlots?: { [key: string]: Function };
model?: {
value: any,
callback: Function
};
}
children
:vnode
的子节点text
:当前节点的文本elm
: 当前虚拟节点对应的真实节点parent
: 当前节点的父节点
看完VNode
在Vue.js
中的数据结构定义,我想你已经大概知道vdom
是什么了吧。
Virtual DOM 是什么?
本质上来说,vdom
只是一个简单的js
对象,并且最少包含tag
、props
和children
三个属性。不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。它们分别是标签名(tag)
、属性(props)
和子元素对象(children)
。下面是举一个经典的vdom
例子:
<div>
Hello jack-cool
<ul>
<li id="1" class="li-1">
我是森林
</li>
</ul>
</div>
vdom
和dom
对象有着一一对应的关系,上面的html
对应生成的vdom
如下:
{
tag: "div",
props: {},
children: [
"Hello jack-cool",
{
tag: "ul",
props: {},
children: [{
tag: "li",
props: {
id: 1,
class: "li-1"
},
children: ["我是", "森林"]
}]
}
]
}
Virtual DOM 有什么作用?
vdom
的最终目标是将vnode
渲染到视图上。但是如果直接使用新节点覆盖旧节点的话,会有很多不必要的DOM
操作。
我们先来看下引入vdom
前后,实现视图更新的不同流程:
引入 vdom 之前
- 数据 + 模板生成真实
DOM
- 数据发生改变
- 新的数据 + 模板生成新的
DOM
- 新的
DOM
替换掉原来的DOM
这么做的缺点在于:即使模板中只有一个元素发生了变化,也会把整个模板替换掉。例如,一个ul
标签下很多个li
标签,其中只有一个li
有变化,这种情况下如果使用新的ul
去替代旧的ul
,会有很多不必要的DOM
操作而造成性能上的损失。
为了避免不必要的DOM
操作,vdom
在vnode
映射到视图的过程中,将vnode
与上一次渲染视图所使用的旧虚拟节点(oldVnode
)做对比,找出真正需要更新的节点来进行DOM
操作,从而避免操作其他无需改动的DOM
。
引入 vdom 之后
- 数据 + 模板生成虚拟
DOM
- 虚拟
DOM
生成真实DOM
- 数据发生改变
- 新的数据 + 模板生成新的虚拟
DOM
而不是真实DOM
- 用新的虚拟
DOM
和原来的虚拟DOM
作对比(diff
算法,后面会详细介绍)【性能up↑
】 - 找出发生改变的元素
- 直接修改原来的真实
DOM
【性能up↑
】
总结
这一节我带大家大概了解了Virtual DOM
的概念。Vue.js
的 VNode
其实是借鉴了 snabbdom
的实现。
从VNode
到真实 DOM
需要经过 create
、diff
、patch
等几个过程。本小节呢,我们只是大概了解一下vdom
是什么以及它有什么作用。关于vdom
的一些详细概念、流程和内部实现,我会在后面的章节中和大家分享(其实关于Virtual DOM
单独出一个系列文章也不为过)。