源码分析系列三:render的执行过程和Virtual DOM的产生"/>
vue源码分析系列三:render的执行过程和Virtual DOM的产生
render
手写 render
函数,仔细观察下面这段代码,试想一下这里的 createElement 参数是什么 。
new Vue({el: '#application',render(createElement) {return createElement('div', {attrs: {id: 'app1' //注意这里的id是app1了不是index.html中的application了 }}, this.value)},data() {return {value: 'render function'}}
})
页面效果:
我们可以看到app1已经替换掉了application,所以我们为什么不能将元素绑定在body/html这些元素上就知道了吧,因为它会替换掉页面上的元素。
源码分析:
Vue 的 _render
方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。它的定义在 src/core/instance/render.js 文件中:
Vue.prototype._render = function (): VNode {const vm: Component = thisconst { render, _parentVnode } = vm.$options// 下面这两个 if 先不用看// reset _rendered flag on slots for duplicate slot checkif (process.env.NODE_ENV !== 'production') {for (const key in vm.$slots) {// $flow-disable-linevm.$slots[key]._rendered = false}}if (_parentVnode) {vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode = _parentVnode// render selflet vnodetry {// 分析主线// 从这里我们可以看出,手写render方法中的createElement参数就是 vm.$createElement方法vnode = render.call(vm._renderProxy, vm.$createElement)// 这个render 是 vm.$options.render// vm._renderProxy 如果在生产环境下,其实就是 vm // 如果在开发环境下,就是 Proxy 对象(ES6中的API,不了解的话可以去看看)// vm.$createElement 定义在 initRender 函数中,初始化的时候定义的// 手写 render 函数创建 vnode 的方法// vm.$createElement = function (a, b, c, d) { // return createElement(vm, a, b, c, d, true);// };// 如果是编译生成的render函数,创建vnode的方法则是下面这个方法// vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };} 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') {if (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}} else {vnode = vm._vnode}}// return empty vnode in case the render function errored outif (!(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 parentvnode.parent = _parentVnodereturn vnode}
}
Flow
是 facebook
出品的 JavaScript 静态类型检查工具。Vue.js
的源码利用了 Flow 做了静态类型检查,function (): VNode
表示这个方法的返回值是一个 vnode
。
再回到 _render
函数中的 render
方法的调用:
vnode = render.call(vm._renderProxy, vm.$createElement)
可以看到,render
函数中的 createElement
方法就是 vm.$createElement
方法:
export function initRender (vm: Component) {// ...// 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 templatesvm._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)
}
实际上,vm.$createElement
方法定义是在执行 initRender
方法的时候,可以看到除了 vm.$createElement
方法,还有一个 vm._c
方法,它是被模板编译成的 render
函数使用,而 vm.$createElement
是用户手写 render
方法使用的, 这俩个方法支持的参数相同,并且内部都调用了 createElement
方法。
vm._render
最终是通过执行 createElement
方法并返回的是 vnode
,它是一个虚拟 Node
。Vue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM
。因此在分析 createElement
的实现前,我们先了解一下 Virtual DOM
的概念。
Virtual DOM
Virtual DOM 这个概念相信大部分人都不会陌生,它产生的前提是浏览器中的 DOM 是很“昂贵"的,为了更直观的感受,我们可以简单的把一个简单的 div 元素的属性都打印出来,如图所示:
可以看到,真正的 DOM
元素是非常庞大的,因为浏览器的标准就把 DOM
设计的非常复杂。当我们频繁的去做 DOM 更新,会产生一定的性能问题。
而 Virtual DOM
就是用一个原生的 JS 对象去描述一个 DOM
节点,所以它比创建一个 DOM
的代价要小很多。在 Vue.js 中,Virtual DOM
是用 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 scopekey: string | number | void;componentOptions: VNodeComponentOptions | void;componentInstance: Component | void; // component instanceparent: VNode | void; // component placeholder node// strictly internalraw: boolean; // contains raw HTML? (server only)isStatic: boolean; // hoisted static nodeisRootInsert: boolean; // necessary for enter transition checkisComment: boolean; // empty comment placeholder?isCloned: boolean; // is a cloned node?isOnce: boolean; // is a v-once node?asyncFactory: Function | void; // async component factory functionasyncMeta: Object | void;isAsyncPlaceholder: boolean;ssrContext: Object | void;fnContext: Component | void; // real context vm for functional nodesfnOptions: ?ComponentOptions; // for SSR cachingfnScopeId: ?string; // functional scope id supportconstructor (tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function) {this.tag = tagthis.data = datathis.children = childrenthis.text = textthis.elm = elmthis.ns = undefinedthis.context = contextthis.fnContext = undefinedthis.fnOptions = undefinedthis.fnScopeId = undefinedthis.key = data && data.keythis.componentOptions = componentOptionsthis.componentInstance = undefinedthis.parent = undefinedthis.raw = falsethis.isStatic = falsethis.isRootInsert = truethis.isComment = falsethis.isCloned = falsethis.isOnce = falsethis.asyncFactory = asyncFactorythis.asyncMeta = undefinedthis.isAsyncPlaceholder = false}// DEPRECATED: alias for componentInstance for backwards compat./* istanbul ignore next */get child (): Component | void {return this.componentInstance}
}
可以看到 Vue.js
中的 Virtual DOM
的定义还是略微复杂一些的,因为它这里包含了很多 Vue.js 的特性。这里千万不要被这些茫茫多的属性吓到,实际上 Vue.js
中 Virtual DOM
是借鉴了一个开源库 snabbdom 的实现,然后加入了一些 Vue.js 特色的东西。我建议大家如果想深入了解 Vue.js
的 Virtual DOM
前不妨先阅读这个库的源码,因为它更加简单和纯粹。
总结
其实 VNode
是对真实 DOM
的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode
的灵活性以及实现一些特殊 feature
的。由于 VNode
只是用来映射到真实 DOM
的渲染,不需要包含操作 DOM
的方法,因此它是非常轻量和简单的。
Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM
实际上要经历 VNode
的 create、diff、patch
等过程。那么在 Vue.js
中,VNode
的 create
是通过之前提到的 createElement
方法创建的,
更多推荐
vue源码分析系列三:render的执行过程和Virtual DOM的产生
发布评论