摘要:作者王聰本篇目的是介紹實例化到掛載到的整體路線,一些細(xì)節(jié)會被省略。從源碼中找到構(gòu)造函數(shù)的聲明,是一個很簡潔的工廠模式聲明的一個構(gòu)造函數(shù)。內(nèi)部做了邏輯判斷構(gòu)造函數(shù)調(diào)用必須有關(guān)鍵字。代表的是當(dāng)前實例也就是構(gòu)造函數(shù)被調(diào)用后的指向。
作者:王聰
本篇目的是介紹vue實例化到掛載到dom的整體路線,一些細(xì)節(jié)會被省略。
所有的一切都是從 new Vue()開始的,所以從這個點開始探尋這個過程發(fā)生了什么。
從源碼中找到Vue構(gòu)造函數(shù)的聲明,src/core/instance/index.js
import { initMixin } from "./init" import { stateMixin } from "./state" import { renderMixin } from "./render" import { eventsMixin } from "./events" import { lifecycleMixin } from "./lifecycle" import { warn } from "../util/index" function Vue (options) { if (process.env.NODE_ENV !== "production" && !(this instanceof Vue) ) { warn("Vue is a constructor and should be called with the `new` keyword") } this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
是一個很簡潔的function工廠模式聲明的一個構(gòu)造函數(shù)。
內(nèi)部做了邏輯判斷:構(gòu)造函數(shù)調(diào)用必須有 new 關(guān)鍵字。
然后執(zhí)行了this._init(options),該初始化函數(shù)就是Vue 初始化的開始。
this._init()是在何時聲明的呢?通過下邊5個初始化函數(shù)的執(zhí)行,在Vue原型鏈中添加了大量的的屬性與函數(shù)。this._init()實際就是調(diào)用了原型鏈上的Vue.prototype._init()函數(shù)。
initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
Vue.prototype._init函數(shù)是在initMixin(Vue)中去添加到原型鏈上的。在src/core/instance/init.js中定義
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== "production") { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, "beforeCreate") initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, "created") /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
_init函數(shù)內(nèi)部又通過上邊的這種調(diào)用初始化函數(shù)的模式完成了具體模塊的初始化。聲明鉤子的調(diào)用也首次在這里出現(xiàn),通過分析這些函數(shù)調(diào)用順序,能更好的理解官方文檔中提及的各個生命周期鉤子函數(shù)的觸發(fā)時機。
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, "beforeCreate") initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, "created")
在_init()函數(shù)的最后,執(zhí)行了vm.$mount()。vm代表的是當(dāng)前vue實例也就是構(gòu)造函數(shù)被調(diào)用后的this指向。
**$mount**是何時在vue上聲明的呢?這次并不是在上邊的初始化函數(shù)中完成聲明的。因為 $mount 這個方法的實現(xiàn)是和平臺、構(gòu)建方式都相關(guān),所以在不同構(gòu)建入口文件中有不同的定義。
原型上聲明的 $mount方法在 src/platform/web/runtime/index.js 中定義,這個方法會被runtime only版本和含編譯器完整版中復(fù)用,vue版本說明。
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
現(xiàn)在接著上邊Vue.prototype._init函數(shù)中調(diào)用了vm.$mount函數(shù),實際上是執(zhí)行了mountComponent(this, el, hydrating)函數(shù)。
mountComponent定義在目錄src/core/instance/lifecycle.js中
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== "production") { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== "#") || vm.$options.el || el) { warn( "You are using the runtime-only build of Vue where the template " + "compiler is not available. Either pre-compile the templates into " + "render functions, or use the compiler-included build.", vm ) } else { warn( "Failed to mount component: template or render function not defined.", vm ) } } } callHook(vm, "beforeMount") let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher"s constructor // since the watcher"s initial patch may call $forceUpdate (e.g. inside child // component"s mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, "beforeUpdate") } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, "mounted") } return vm }
拋開源碼中對性能測試和異常警告部分的代碼,這個函數(shù)內(nèi)部做了以下事情:
callHook(vm, "beforeMount"),調(diào)用生命周期鉤子beforeMount
聲明了updateComponent函數(shù)
new Watcher (vm, updateComponent, noop, {... callHook(vm, "beforeUpdate")}})
callHook(vm, "mounted"),調(diào)用生命周期鉤子mounted
這里的new Watcher()實例化了Watcher類,內(nèi)部邏輯先不去深入,這里僅僅需要知道的是在這個實例化的過程中調(diào)用了作為參數(shù)傳入的updateComponent函數(shù),而從這個函數(shù)的聲明來看,它實際上執(zhí)行的是vm._update(vm._render(), hydrating)這個函數(shù)。
首先vm._update和vm._render這兩個方法是定義在Vue原型上的。
Vue 的 _render 方法是用來把實例渲染成一個虛擬 Node。是在renderMixin(Vue)執(zhí)行時聲明的。它的定義在 src/core/instance/render.js 文件中
Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== "production") { for (const key in vm.$slots) { // $flow-disable-line vm.$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 self let vnode try { 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") { 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 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 }
函數(shù)內(nèi)部對不同邏輯有不同的處理,但最終返回的都是VNode。
Virtual DOM 就是用一個原生的 JS 對象去描述一個 DOM 節(jié)點。
_update是在lifecycleMixin(Vue)函數(shù)執(zhí)行時添加的。在目錄src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent"s updated hook. }
通過源碼看接受的參數(shù):
vnode: VNode
hydrating?: boolean
接受的第一個參數(shù)類型是VNode(用來描述dom節(jié)點的虛擬節(jié)點),第二個布爾類型的參數(shù)是跟ssr相關(guān)。
函數(shù)內(nèi)部最關(guān)鍵的執(zhí)行了vm.$el = vm.__patch__(...)
調(diào)用 vm.__patch__ 去把 VNode 轉(zhuǎn)換成真正的 DOM 節(jié)點
現(xiàn)在回顧總結(jié)一下目前的流程:
new Vue()操作后,會調(diào)用Vue.prototype._init()方法。完成一系列初始化(原型上添加方法和屬性)
執(zhí)行Vue.prototype.$mount
vm._render()獲取描述當(dāng)前實例的VNode
vm._update(VNode),調(diào)用 vm.__patch__轉(zhuǎn)換成真正的 DOM 節(jié)點
流程圖:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103564.html
摘要:最后判斷有無根節(jié)點,無則表示首次掛載,添加鉤子函數(shù),返回總結(jié)實例初始化掛載方法屬性初始化掛載過程在版本,生成函數(shù)對作處理,執(zhí)行中定義了通過實例化的回調(diào)執(zhí)行執(zhí)行,即調(diào)用了真實渲染成對象。 vue 入口 從vue的構(gòu)建過程可以知道,web環(huán)境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compile...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理源碼版之掛載組件由這篇文章從模 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理白話版從模板上使用到掛載到頁面 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理從模板到的簡要流程今天的計劃是, 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基...
閱讀 2659·2023-04-25 15:22
閱讀 2832·2021-10-11 10:58
閱讀 1052·2021-08-30 09:48
閱讀 1857·2019-08-30 15:56
閱讀 1734·2019-08-30 15:53
閱讀 1099·2019-08-29 11:16
閱讀 1054·2019-08-23 18:34
閱讀 1642·2019-08-23 18:12