Vue原理系列之四:渲染函数 render() 执行后生成虚拟节点 vnode
VNode
概念
虚拟节点(virtual node, 简写VNode),一个vnode就是一个js对象(通过VNode实例而来),用来描述一个真实DOM元素,它所包含的信息会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息。 而虚拟DOM是对Vue组件树建立起来的整个VNode树的称呼。
先来看看VNode类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| var VNode = function VNode( tag, data, children, text, elm, context, componentOptions, asyncFactory ) { 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的作用
由于每次渲染视图时都是先创建vnode,然后使用它创建的真实DOM插入到页面中,所以可以将上一次渲染视图时先所创建的vnode先缓存起来,之后每当需要重新渲染视图时,将新创建的vnode和上一次缓存的vnode对比,查看他们之间有哪些不一样的地方,找出不一样的地方并基于此去修改真实的DOM。
Vue.js目前对状态的侦测策略采用了中等粒度。当状态发生变化时,只通知到组件级别,然后组件内使用虚拟DOM来渲染视图。

如果组件只有一个节点发生了变化,那么重新渲染整个组件的所有节点,很明显会造成很大的性能浪费。因此,对vnode进行缓存,并将上一次的缓存和当前创建的vnode对比,只更新有差异的节点就变得很重要。这也是vnode最重要的一个作用。
VNode的类型
vnode类型有以下几种:
注释节点
1 2 3 4 5 6 7 8
| var createEmptyVNode = function (text) { if (text === void 0) text = ""; var node = new VNode(); node.text = text; node.isComment = true; return node; };
|
一个注释节点只有两个有效属性text 和isComment,其余属性全是默认undefined或者false
1 2 3 4 5
| { text: "注释节点", isComment: true }
|
文本节点
1 2 3
| function createTextVNode(val) { return new VNode(undefined, undefined, undefined, String(val)); }
|
元素节点
元素节点通常会存在以下4中有效属性。
- tag:tag就是一个节点的名称,例如 p、ul、li和div等。
- data:改属性包含了一些节点上的数据,比如attrs、class和style等。
- children:当前节点的子节点列表。
- context:它是当前组件的Vue.js实例
比如
1
| <p><span>Hello</span><span>World</span></p>
|
对应的vnode:
1 2 3 4 5 6 7
| { children: [VNode, VNode], context: {...}, data: {...}, tag: "p", ... }
|
组件节点
组件节点有以下两个独有的属性:
componentOptions:组件节点的选项参数,其中包含了propsData、tag和children等信息
componentInstance:组件的实例,也就是Vue.js的实例。事实上,在Vue.js中,每个组件都有一个Vue.js实例
比如<child></child>,对应的vnode
1 2 3 4 5 6 7 8
| { componentInstance: {...}, componentOptions: {...}, context: {...}, data: {...}, tag: "vue-component-1-child", ... }
|
函数式组件
函数式节点有两个独有的属性functionalContext和functionalOptions
1 2 3 4 5 6 7
| { functionalContext: {...}, functionalOptions: {...}, context: {...}, data: {...}, tag: "div" }
|
克隆节点
克隆节点是将现有节点的属性赋值到新节点中,让新创建的节点和被克隆的节点的属性保持一致,从而实现克隆效果。它的作用是优化静态节点和插槽节点(slot node)。
以静态节点为例,当组件内某个状态发生变化后,当前组件会通过虚拟DOM重新渲染视图,静态节点因为它的内容不会改变,所以除了首次渲染需要执行渲染函数获取vnode之外,后续更新不需要执行渲染函数重新生成vnode。
因此,这是就会使用创建克隆节点的方法将vnode克隆一份,使用克隆节点进行渲染。这样就不需要执行渲染函数生成新的静态节点的vnode,从而提升一定的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function cloneVNode(vnode) { var cloned = new VNode( vnode.tag, vnode.data, 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; }
|
生成一个新的vnode的过程
通常使用createElement用来创建一个虚拟节点。
当data上已经绑定__ob__的时候,代表该对象已经被Oberver过了,所以创建一个空节点。tag不存在的时候同样创建一个空节点。当tag不是一个String类型的时候代表tag是一个组件的构造类,直接用new VNode创建。当tag是String类型的时候,如果是保留标签,则用new VNode创建一个VNode实例,如果在vm的option的components找得到该tag,代表这是一个组件,否则统一用new VNode创建。