render
Vue的_render方法是实例的一个私有方法,他用来吧实例渲染成一个虚拟node。他的定义在src/core/instance/render.js文件中
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
这段代码最关键的是render方法的调用,我们在平时的开发工作中手写render方法的场景比较少,而写的比较多的是template模板,在之前的mounted方法的实现中,会把template编译成render方法,但这个编译过程是非常复杂的。
在Vue的官方文档中介绍了render函数的第一个参数是createElement,那么结合之前的例子:
<div id='app'>
{{message}}
</div>
相当于我们编写如下render函数:
render:function (createElement) {
return createElement('div', {
attrs: {
id: "app"
}
}, this.message)
}
在回到_render函数中的render方法的调用:
vnode = render.call(vm._renderProxy, vm.$createElement)
可以看到,render函数中的createElement方法就是vm.$createElement方法:
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
实际上,vm.createElement方法,还有一个vm._c方法,他是被模板编译成的render函数使用,而vm.$createElement是用户手写render方法使用的,这两个方法支持的参数相同,并且内部都调用了createElement方法。
总结
vm._render最终是通过执行createElement方法返回的是vnode,他是一个虚拟Node。Vue2.0相比Vue1.0最大的升级就是李永利VirtualDOM,因此在分析createElement的实现前,我们先了解一下VirtualDOM的概念
Virtual DOM
Virtual DOM这个概念相信大部分都不会陌生,它产生的前提是浏览器中的DOM非常昂贵,为了更直观的感受,我们可以简单的把一个简单的div的属性都打印出来,如图所示:
可以看到,真正的DOM元素是非常庞大的,因为浏览器的标准就是吧DOM设计得非常复杂。当我们频繁的去做DOM的更新,会产生一定的性能问题。
而VirtualDOM就是用一个原生的JS对象去描述一个DOM节点,所以他比创建一个DOM的代价要小很多。在Vue.js中,VirtualDOM是用VNode这么一个Class去买哦书,他是定义在src/core/vdom/vnode.js中的
export default 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
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
可以看到Vue.js中的VirtualDOM的定义还是略微复杂一些的,因为他这里包含了很多Vue.js的特性。这里千万不要被这些茫茫多的属性给吓到,实际上Vue.js中VirtualDOM是借鉴了一个开源库snabbdom的实现,然后加入了一些Vue.js特色的东西,我建议大家如果想深入了解Vue.js的VirtualDOM钱不妨先阅读一下这个库的源码,因为他更加简单和纯粹。
总结
其实VNode是对真实DOM的一种抽象描述,他的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其实属性都是用来扩展VNode的陵侯兴以及实现一些特殊的feature的。由于VNode只是用来映射真实DOM的渲染,不需要包含操作DOM的方法,因此他是非常轻量和简单的。
Virtual DOM除了他的数据结构的定义,映射到真实的DOM实际上要经历VNode的create、diff、patch等过程。那么在Vue.js中,VNode的create是通过之前提到的createElement方法创建的。