虚拟DOM
的概念
虚拟DOM(Virtual DOM)
是使用js
对象描述真实DOM
。通过对比变化前后的虚拟DOM
,可以精确定位到视图的变化,只修改变化部分的视图,提高复杂视图变化时的渲染效率。
Vue
中的虚拟DOM
Vue
中的虚拟DOM
借鉴了Snabbdom
,其模块机制,钩子函数以及diff
算法和Snabbdom
几乎一致,但Snabbdom
的基础上添加了Vue
的特性,如指令和组件机制。
虚拟DOM
的优缺点
优点
- 避免直接操作
DOM
,提交开发效率。 - 作为中间层可以实现跨平台。
- 复杂视图情况下可以提升渲染性能
缺点
- 首次渲染会增加开销,因为要维护额外的虚拟
DOM
。 - 简单视图情况下会增加额外的成本。在理想状态下,每次数据的修改都能直接定位到视图的修改,这样比使用虚拟
DOM
效率更高,消耗的资源更少。
render
函数中的参数h
函数
render
函数中的参数h
函数和Snabbdom
中的h
函数是类似的,用来创建虚拟DOM
,接收四个参数,前三个参数和Snabbdom
中的h
函数一致,第一个是字符串类型的标签或者选择器,第二个参数是一个可选的选项对象,第三个参数是表示子元素,可以是一个字符串或一个数组,也是可选的。
const vm = new Vue({
el: '#app',
render (h) {
// h(tag, data, children)
// return h('h1', this.msg)
// return h('h1', { domProps: { innerHTML: this.msg } })
// return h('h1', { attrs: { id: 'title' } }, this.msg)
const vnode = h(
'h1',
{
attrs: {
id: 'title' }
},
this.msg
)
console.log(vnode)
return null
},
data: {
msg: 'Hello Vue'
}
})
h
函数即vm.$createElement
方法。
h
函数最终返回一个VNode
对象(即虚拟DOM
对象)。包括以下核心属性:
tag
:调用h
函数传入的第一个参数,表示要创建节点的选择器。data
:调用h
函数传入的第二个参数,描述节点的属性对象。children
:调用h
函数传入的第三个参数,表示节点的子元素信息。text
:节点的文本内容。elm
:节点对应的真实DOM
,节点转化为真实DOM
之后将保存在该属性。key
:节点标记,为了复用节点。
VNode
的创建过程
即createElment
的调用过程:
-
mountComponent
中定义了updateComponent
,创建Watcher
时会将updateComponent
传入,数据变化时会调用updateComponent
重新渲染。而updateComponent
内部则是调用了vm._render
方法。
-
_render
方法时在instance/render.js
文件中定义的。方法内部是通过调用用户传入的render
方法来创建虚拟DOM
对象。调用render方法时会传入vm.$createElement
(即h
函数)作为参数//vm._renderProxy = vm vnode = render.call(vm._renderProxy, vm.$createElement)
-
vm.$createElement
内部调用createElement
方法,其内部将调用_createElement
方法生成虚拟DOM
,_createElement
方法实现如下:export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${ JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // <component v-bind:is="currentTabComponent"></component> // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !('@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } } // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || { } data.scopedSlots = { default: children[0] } children.length = 0 } if (normalizationType === ALWAYS_NORMALIZE) { //用户传入的render函数 // 返回一维数组,处理用户手写的 render children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { // 把二维数组,转换成一维数组 children = simpleNormalizeChildren(children) }