上回咱們提到,在子組件存在的狀況下,父組件在執行完created
鉤子函數以後生成子組件的實例,子組件執行created
鉤子函數,同時也檢查是否也有子組件,有則重複父組件的步驟,不然子組件的dom
元素渲染
vnode
在上一篇文章中其實咱們提到一個函數 —— createComponentInstanceForVnode
👇javascript
function createComponentInstanceForVnode ( vnode, // we know it's MountedComponentVNode but flow doesn't parent // activeInstance in lifecycle state ) { var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // check inline-template render functions var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } return new vnode.componentOptions.Ctor(options) }
與之相關的代碼👇前端
... var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ); child.$mount(hydrating ? vnode.elm : undefined, hydrating);
從中咱們能夠得知:java
createComponentInstanceForVnode
生成的vnode.componentOptions.Ctor(options)
有關VNode
經過全局檢索componentOptions
,可知存在以下代碼👇node
var VNode = function VNode ( tag, data, children, text, elm, context, componentOptions, asyncFactory ) { this.tag = tag; this.data = data; this.children = children; this.text = text; this.elm = elm; ... }
實際上,在beforeMount
鉤子和mounted
鉤子之間,有段奇怪的代碼😅瀏覽器
new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */);
看過前面的文章的你其實已經知道Watcher
的執行邏輯:微信
getter
屬性value
賦值的同時執行getter
附updateComponent
實現:閉包
updateComponent = function () { vm._update(vm._render(), hydrating); };
這意味着函數 updateComponent
將被執行,同時存在這樣的調用順序(從上往下執行):app
vm._render
vm_update
同時dom
元素確定也是在這兩個函數調用時渲染dom
vm._render
Vue.prototype._render = function () { var vm = this; var ref = vm.$options; var render = ref.render; var _parentVnode = ref._parentVnode; 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 var vnode; try { // There's no need to maintain a stack becaues 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
的執行過程(從上往下):async
_parentVnode
(父組件的 vnode
) 賦值給 vm.$vnode
normallizeScopedSlots
,將父子組件的 $slots
跟 $scopedSlots
合併render
函數並賦值給 vnode
(即獲得現有的 vnode
)vnode
爲空則執行 createEmptyVNode
函數vnode
這裏咱們優先把斷點打入 render
函數,理所固然的會獲得如下執行過程:
render
vm.$createElement
因爲最早執行的是 new Vue({...})
,因此看上去斷點好像停在了「奇怪的地方」👇
new Vue({ render: h => h(App), }).$mount('#app')
render
函數細心的同窗會注意到這樣一行代碼👇
vnode = render.call(vm._renderProxy, vm.$createElement);
步進以後斷點立刻跳到了這裏👇
new Vue({ render: h => h(App), ... }).$mount('#app')
其實,這裏將 vm._renderProxy
做爲了 render
函數的上下文對象,而 vm.$createElement
返回一個閉包函數做爲 render
函數的參數傳入
相關代碼:
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
總結下生成 vnode
的完整邏輯:
$mount
函數dom
元素並賦值給 el
變量,不然 el
變量取值 undefined
mountComponent
函數new Watcher(vm, updateComponent, noop, ...)
Watcher
的「特性」(傳入的 updateComponent
賦值給 getter
以後執行),_render
函數在這以後會被觸發$createElement
createElement
_createElement
data
及 data.is
是否不爲空,是則將 data.is
賦值給 tag
tag
爲空,那麼認爲這是一個空白節點,此時調用 createEmptyVNode
建立一個「空白節點」,並把 isComment
標記爲 true
tag
是不是「保留字」,是則屬於 HTML
標籤,生成對應的 vnode
,不然調用 createComponent
函數生成對應的 vnode
vnode
createEmptyVNode
var createEmptyVNode = function (text) { if ( text === void 0 ) text = ''; var node = new VNode(); node.text = text; node.isComment = true; return node };
掃描下方的二維碼或搜索「tony老師的前端補習班」關注個人微信公衆號,那麼就能夠第一時間收到個人最新文章。