Virtual DOM 这个概念相信大部分人都不会陌生,它产生的前提是浏览器中的 DOM 是很“昂贵”的,为了更直观的感受,我们可以简单的把一个简单的 div 元素的属性都打印出来,如图所示:
上图中我们打印一个简单的空 div 标签,就打印出这么多东西,更不用说复杂的、深嵌套的 DOM 节点了。由此可见,直接操作真实 DOM 是非常消耗性能的。 而 Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去描述,它是定义在 src/core/vdom/vnode.js 中的。
// 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
// DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ getchild(): Component | void { returnthis.componentInstance; } }
可以看到 Vue.js 中的 Virtual DOM 的定义还是略微复杂一些的,因为它这里包含了很多 Vue.js 的特性。这里千万不要被这些茫茫多的属性吓到,实际上 Vue.js 中 Virtual DOM 是借鉴了一个开源库 snabbdom 的实现,然后加入了一些 Vue.js 特色的东西。我建议大家如果想深入了解 Vue.js 的 Virtual DOM 前不妨先阅读这个库的源码,因为它更加简单和纯粹。
VNode 的类型
VNode 类可以通过不同属性的搭配来描述出各种类型的真实 DOM 节点。那么它都可以描述出哪些类型的节点呢?通过阅读源码,发现可以描述出以下几种类型的节点。
相比之下,元素节点更贴近于我们通常看到的真实 DOM 节点,它有描述节点标签名词的 tag 属性,描述节点属性如 class、attributes 等的 data 属性,有描述包含的子节点信息的 children 属性等。由于元素节点所包含的情况相比而言比较复杂,源码中没有像前三种节点一样直接写死(当然也不可能写死),那就举个简单例子说明一下:
我们可以看到,真实 DOM 节点中:div 标签里面包含了一个 span 标签,而 span 标签里面有一段文本。反应到 VNode 节点上就如上所示:tag 表示标签名,data 表示标签的属性 id 等,children 表示子节点数组。
组件节点
组件节点除了有元素节点具有的属性之外,它还有两个特有的属性:
componentOptions :组件的 option 选项,如组件的 props 等
componentInstance :当前组件节点对应的 Vue 实例
函数式组件节点
函数式组件节点相较于组件节点,它又有两个特有的属性:
fnContext:函数式组件对应的 Vue 实例
fnOptions: 组件的 option 选项
总结
其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。
Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。有了数据变化前后的VNode,我们才能进行后续的DOM-Diff找出差异,最终做到只更新有差异的视图,从而达到尽可能少的操作真实DOM的目的,以节省性能。