距離本小白接觸前端開發也已經快將近一年了,從開始的連閉包的原理都須要瞭解一個晚上(雖然目前深刻其原理我也說不明白)到如今可以本身獨立完成一個簡單的單頁webapp,回顧近一年的時間,也是按照前人留下的足跡,一步一個腳印的走,從html到css再到js再到jQuery庫,再到webpack,直到Vue。一路走來較爲順利,單也有磕絆,也有疑惑。開始接觸Vue的時候,甚爲驚豔!居然有如此之寫法,簡潔,方便。可是使用過程固然難免充滿了疑惑,爲何是這樣的?爲何能這樣寫。在通過近一年的打磨以後,本小白也決定開始研究、研讀Vue源碼,但願在Vue3.0來臨之際,將2.6.X源碼研究一遍,爲日後3.0打基礎,而且將這一年來心中的很多疑惑悉數解開!也但願本文對一樣在Vue道路上的朋友有所幫助~javascript
磨刀不誤砍柴工,再開始研讀以前,咱們須要瞭解一下Vue的文件的架構以及Vue源碼編寫使用到的靜態類型檢查器FLOW。css
咱們從github上面下載最新的vue的源碼後,能夠看到vue的源碼項目文件仍是很是龐大的,和通常的webpack相似,咱們Vue源碼也存在src文件當中。 html
compiler文件夾存放的代碼是與模板解析相關的後續的文章也會專門講到。core文件就是咱們這一篇要講得的核心也是Vue實例初始化的核心所在。前端
platform文件夾存放的是咱們的vue如何進入到運行時的,區分是經過cli打包的文件仍是直接在html中引用的狀況等。vue
server文件中存放生成render模板編譯的相關代碼,例如咱們的template是如何編譯成render函數,邏輯在這個文件夾中。java
FLOW是一個靜態類型檢查器,因爲Vue中存在大量的類,引入flow靜態檢查器來確保類屬性不出錯,用法相似於TypeScript,這裏就很少展開介紹了(實際上是不會)。node
正文開始了!首先,咱們找到Vue類定義的位置src/core/instance/index.js
。webpack
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 複製代碼
在這裏看到Vue類的定義很是"簡單"= =;作了一個判斷防止調用Vue函數,而後就是一個初始化this._init(options)
。而_init 就是在initMixin(Vue)
是混入到類中的。options就是咱們常常main.js寫的那個傳入的對象:git
new Vue({ render: h => h(App), }).$mount('#app') 複製代碼
那麼,接下來 咱們就要看一下initMixin到底對Vue作了什麼,給這個類添加了什麼功能,以及_init()函數到底作了哪些事情。github
從上面代碼能夠看到initMixin
在同級目錄下init.js文件中,咱們來看一下initMixin
到底幹了什麼。
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { //some codes } } export function initInternalComponent (vm: Component, options: InternalComponentOptions) { //some codes } export function resolveConstructorOptions (Ctor: Class<Component>) { //some codes } function resolveModifiedOptions (Ctor: Class<Component>): ?Object { //some codes } 複製代碼
好吧整個文件去掉邏輯部分仍是很清晰的,暴露四個方法,能夠看到initMixin
就是隻爲Vue添加了一個_init
方法,那麼接下來咱們就重點來看一下_init方法,在Vue實例化的過程當中,作了什麼吧~
首先,先把源碼貼上~
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) //實例的首次掛載 } } 複製代碼
咱們一點點來說,首先判斷咱們傳入的options,根據是否存在options._isComponent
來判斷如何處理options。Vue的初始化邏輯固然是走下面的,經過mergeOptions()
合併了父類(這裏是指Vue)options和當前options。那麼好奇的童鞋確定要問了,Vue哪來的options??!!這裏賣個關子,後面會講到的~仍是比較重要的一塊,因此留個懸念,也算是加深印象~
能夠看到接下來就是一系列的init初始化,咱們本章大體介紹一下每一個init都是幹啥的,具體展開討論會放到後面專門的文中介紹~首先是initLifecycle
:它主要是針對生命週期作一些初始化;initEvents
:對vue的監聽事件的初始化;initRender
:初始化Vue的render方法,本文會花必定篇幅展開;callhook:beforeCreate
:調起預先設定的beforeCreate鉤子函數(沒錯生命週期鉤子函數就在這裏慢慢開始了);initInjections(vm)
:後代組件injection相關初始化;initState(vm)
:初始化data、props、methods等屬性,數據雙向綁定的核心所在,以後文章會詳細展開。initProvide(vm)
:後代組件initProvide相關初始化;callHook(vm, 'created')
:調起預先設定的created鉤子函數。
vm.$mount(vm.$options.el)
最後一步就是調用vm.$mount()
方法進行實例的掛載。咱們能夠看到,若是咱們傳入的屬性若是有el屬性,纔會調用,否則是不會調用的。因此咱們在new Vue是會有兩種狀況:
new Vue({ render: h => h(App), }).$mount('#app')//不走_init()的mount本身調用 new Vue({ el:'#app'//_init直接調用mount render: h => h(App), }) 複製代碼
到這裏,_init(options)
的流程也就結束了,雖而後面的文章咱們還會無數次提到他。。。接下來咱們就來一塊兒看一下vm.$mount()
到底幹了些什麼,以及具體他是如何掛載咱們的Vue實例的。
vm.$mount(el)
定義在src/platforms/web/index.js
咱們一塊兒來看一下代碼。
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } 複製代碼
看來只是一個封裝,咱們繼續去找mountComponent(this, el, hydrating)
,代碼在src/core/instance/lifecycle.js
裏面。咱們看一下代碼:
export function mountComponent ( //vue實例掛載的方法 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) //掛載實例,調用 } } 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 } 複製代碼
咱們一步步看他的邏輯來看他的邏輯。一、!vm.$options.render
判斷若是實例不存在render函數,則建立一個空的vnode
。二、callHook(vm, 'beforeMount')
原來這個鉤子函數在這兒~三、調用了_update()裏面兩個參數,vm._render()建立了一個node節點,接下來咱們會重點展開。
let updateComponent () => { vm._update(vm._render(), hydrating) } 複製代碼
四、new Watcher()添加了一個觀察者,這是一個render Watcher監聽數據變化後展開節點渲染。(後面介紹watcher的時候會重點講)目前咱們只要知道watcher在初始化會調用傳入的updateComponent函數,從而觸發渲染和實例掛載。五、callHook(vm, 'mounted')
:mounted鉤子函數都不陌生。到這裏vm.$mount函數也就走完了!
原本打算把vm._update(vm._render(), hydrating)
也放在這裏面講的,可是一想,若是倉促展開,可能有些細節回顧及不到並且vm._update()
和vm._render()
也是比較重要的兩塊一塊涉及patch(確定就會涉及到diff算法)另外一塊涉及到Vnode建立,如今本小白也處於比較沒有思緒的狀況,不知如何展開你們會比較容易接受,須要再整理整理大綱在接着往下來寫~因此就先到這兒吧!在這裏也貼一下目前本小白學習整理的大綱:Vue源碼解讀
有大佬們須要短信業務,或者號碼認證的能力的話,能夠看看這裏!中國聯通創新能力平臺 運營商官方平臺!沒有中間商賺差價~