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 方法的調用:
可以看到,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的产生的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1033,2052 是什么意思?
- 下一篇: vue异常报错解决 Missing re