我的博客地址vue
虚拟DOM是随着时代发展而诞生的产物。node
在web早期,都是命令式的操做DOM,虽然简单好用,可是不会维护。web
如今,三大主流框架都是声明式的操做DOM,经过描述状态和DOM之间的映射关系,来渲染成视图。状态怎么生成视图,不须要你来关心,框架会帮你搞定。算法
当某个状态发生改变时,如何只更新与这个状态相关联的DOM节点。框架
虚拟DOM的解决方式是:根据状态生成一个虚拟节点树,而后使用虚拟节点树进行渲染。在渲染前会将新的虚拟节点树和旧的虚拟节点树进行对比,只渲染不一样的地方。async
虚拟节点树是由组件树创建起来的整个虚拟节点(vnode)树。函数
vnode是JavaScript中一个很普通的对象,这个对象上保存了生成DOM节点须要的一些数据。性能
在Angular和 React中,它们只知道有状态(state)变化了,可是不是到具体是哪一个或者哪些状态(state)变化了,因此就须要进行比较暴力的对比,React是经过虚拟Dom进行对比,Angular是使用脏检查流程。ui
Vue的变化侦测和这两个都不同,Vue在必定程度是知道哪些状态发生了变化。可是若是细粒度太细,每个绑定都会有一个watcher实例来观察状态的变化,这样就会有一些内存开销以及一些依赖追踪的开销。当状态越多的节点被使用时,开销就越大。this
所以在Vue2把细粒度调整到中,组件级别是一个watcher实例,无论组件内部使用了多少次,所以当状态变化时,只通知到组件,在组件内部使用虚拟DOM去进行对比更新。
在vue中使用模板来描述状态和DOM之间的映射关系,根据模板生成render渲染函数,执行render渲染函数生成虚拟节点树,而后再与旧的虚拟节点树对比,最后渲染DOM。
虚拟DOM在vue中主要作了两件事:
对两个虚拟节点进行对比是整个虚拟DOM中最核心的算法,它能够判断出那个节点发生了变动,从而只更新变动的节点。
Vue 中有一个 VNode 类,能够实例化不一样类型的 vnode,不一样类型的 vnode 表示不一样类型的 DOM 元素:
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
}
}
复制代码
简单的说,vnode 能够理解为节点描述对象,描述了怎么去建立一个真实的DOM,DOM元素上的全部属性堵在 vnode 上有对应的属性。
vnode 表示一个真正的 DOM 元素全部真实的 DOM 元素都使用 vnode 建立,并插入到视图中去。
每次渲染视图时,都会建立新的 vnode ,与旧的 vnode 进行对比,找出不同的地方并更新。
Vue 目前采用的是中等密度的细粒度,当状态变化时,只会通知到组件,在组件内部使用虚拟DOM来渲染视图。
也就是说只要组件内部有一个状态发生了改变了,整个组件都会从新渲染。若是组件只变化了一个节点,却要从新渲染全部的节点,这会形成性能浪费。
所以对比新旧 vnode ,只更新不一样的部分,就显得尤其重要。
注释节点
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
复制代码
文本节点
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}
复制代码
元素节点
元素节点一般包括一下4种属性:
组件节点
和元素节点差很少,有两个特有属性:
函数式节点
和组件节点相似,有两个特有属性:
克隆节点
克隆节点是将现有节点的全部属性都复制到新节点下面,直接复用现有的 vnode ,出了首次渲染外,后续更新都不须要执行渲染函数生成相同的 vnode ,从而提高必定程度的性能。
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.asyncMeta = vnode.asyncMeta
cloned.isCloned = true
return cloned
}
复制代码
总结:
在 Vue 中,VNode 是一个类,不一样类型的 VNode 实例表明不一样类型的元素节点。
Vue2 对状态的侦测策略采用了中等粒度,当状态发生变化时,只会通知到组件,组件内部再使用虚拟DOM进行更新。组件内不是全部的节点都须要更新,因此将渲染函数新生成的 vnode 和旧的 vnode 进行对比,只更新不一样的部分。
下一篇会介绍虚拟DOM最核心的算法:patch。