生成vnode
大家知道一个复杂的页面会包含大量的DOM节点,为了高效地更新这些DOM节点,vue设计了虚拟DOM的概念。虚拟DOM是对真实DOM节点信息的描述。在vue中,每一个DOM节点都会有一个虚拟DOM节点与之对应。这个虚拟DOM节点,我们也称之为vnode,而由vnode所组成的整个vnode树就是虚拟DOM。
还是通过一个例子来看vnode的创建。假设给定如下模板:
<div id="app" @click="add">{{count}}</div>
经过编译后我们可以得到如下render函数:
function render( ) { with(this){ return _c('div', {attrs:{"id":"app"}, on:{"click":add}}, [_v(_s(count))]) } }
函数 _c
是在初始化render环境的时候添加到vue实例上,用来创建 vnode
的全局实例方法。它可以通vue实例直接调用,主要是给vue内部使用的vnode创建方法。其底层实现上与vue对外向用户暴露的API $createElement
一样都是调用了内部的 _createElement
方法。_createElement
核心代码如下:
function _createElement(context, tag, data, children, normalizationType){ // ...省略其他代码 let vnode = new VNode(tag, data, children, undefined, undefined, context) // ...省略其他代码 return vnode }
一个vnode节点主要包含如下信息:
{ // 元素标签,如div tag, // 数据对象例如,{attrs: {id: 'app'}} data, // vnode 子节点数组 children, // 元素包含的文本 text, // 所对应的dom节点 elm, // 所对应的vue实例 context, // 父节点vnode parent, //...省略其他 }
方法 _v
也是vue实例方法,内部用以创建文本类型的vnode,在本例中,{{count}}
是一个文本节点,所以需要使用 _v
来创建文本vnode。不过无论是文本类型的vnode还是非文本类型的vnode都是Vnode对象的实例。两者的区别在于,文本类型的vnode不存在 tag
和 children
。
// 创建一个文本类型的VNode function createTextVNode (val) { return new VNode(undefined, undefined, undefined, String(val)) }
方法 _s
同样也是vue的实例方法,内部用来将接收的参数变成字符串返回,对于字符串和数值使用 Object.toString()
转换,如果接收到的是一个对象,则使用 JSON.stringify()
转换。
function toString (val){ return val == null ? '' : Array.isArray(val) || (isPlainObject(val) && val.toString === Object.prototype.toString) ? JSON.stringify(val, null, 2) : String(val) }
vnode 通过 parent
和 children
连接父节点和子节点,组成vnode树。
创建DOM节点
有了vnode后,vue还需要根据vnode来创建DOM节点。如果是首次渲染,那么vue会走创建的逻辑。如果是数据的更新导致的重新渲染,那么vue会走更新的逻辑。