Vue源碼探究-核心類的實現

Vue源碼探究-核心類的實現

本篇源代碼所在路徑vue/src/core/instance/

幾乎所有JS框架或插件的編寫都有一個類似的模式,即向全局輸出一個類或者說構造函數,通過創建實例來使用這個類的公開方法,或者使用類的靜態全局方法輔助實現功能。相信精通Jquery或編寫過Jquery插件的開發者會對這個模式非常熟悉。Vue.js也如出一轍,只是一開始接觸這個框架的時候對它所能實現的功能的感嘆蓋過了它也不過是一個內容較爲豐富和精緻的大型類的本質。

核心類

Vue的核心類的構建文件,代碼非常簡單,就是一串定義構造函數的基礎代碼:

// 定義Vue構造函數,形參options
function Vue (options) {
  // 安全性判斷,如果不是生產環境且不是Vue的實例,在控制檯輸出警告
  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)
}

但是Vue所有功能的實現,這只是一個開始:

// 引入初始化混合函數
import { initMixin } from './init'
// 引入狀態混合函數
import { stateMixin } from './state'
// 引入視圖渲染混合函數
import { renderMixin } from './render'
// 引入事件混合函數
import { eventsMixin } from './events'
// 引入生命週期混合函數
import { lifecycleMixin } from './lifecycle'
// 引入warn控制檯錯誤提示函數
import { warn } from '../util/index'
...

// 掛載初始化方法
initMixin(Vue)
// 掛載狀態處理相關方法
stateMixin(Vue)
// 掛載事件響應相關方法
eventsMixin(Vue)
// 掛載生命週期相關方法
lifecycleMixin(Vue)
// 掛載視圖渲染方法
renderMixin(Vue)

在類構造文件的頭部引入了同目錄下5個文件中的混合函數(我認爲這裏只是爲了要表示把一些方法混入到初始類中才統一用了Mixin的後綴,所以不要深究以爲這是什麼特殊的函數),分別是初始化 initMixin 、狀態 stateMixin 、渲染 renderMixin、事件 eventsMixin、生命週期 lifecycleMixin。在文件尾部將這幾個函數裏包含的具體方法掛載到Vue原始類上。

從各個細化模塊,可以看出作者是如何進行邏輯架構分類的。這裏又學到了一種模塊開發的好方法,將類繼承方法按模塊獨立編寫,單獨進行掛載實現了可插拔的便利性。

export default Vue

文件最後的經典代碼。到此Vue的類構造完成!

就這樣完成了麼!且慢,來稍微看一下初始化混合函數初步做了些啥:

初始化的過程

下面代碼位於vue/src/core/instance/init.js

最先爲基礎類掛載的方法就是_init(),這是唯一在類實例化的過程中執行的函數,位於整個函數棧的最底層,其他的功能將在此方法裏初步分化。

// 導出ininMixin函數,接收形參Vue,
// 使用Flow進行靜態類型檢查指定爲Component類
export function initMixin (Vue: Class<Component>) {
  // 在Vue類的原型上掛載_init()方法
  // 接收類型爲原始對象的options形參,此參數爲非必選參數
  Vue.prototype._init = function (options?: Object) {
    // 將實例對象賦值給vm變量
    // 這裏會再次進行Component類型檢查確保vm接收到的是Vue類的實例
    const vm: Component = this
    // 給實例對象vm定義_uid屬性,作爲vue實例的唯一標識ID
    // uid是在函數外定義的變量,從0開始增量賦值
    // a uid
    vm._uid = uid++
    // 定義startTag、endTag變量
    let startTag, endTag
    // 註釋的意思是代碼覆蓋率檢測工具istanbul會忽略if分支
    // 因爲下面代碼是專爲性能分析使用的,以後都不做分析
    /* 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是進行性能分析的工具函數,目前可忽略
      mark(startTag)
    }
    // 給vm設置一個_isVue屬性作爲標記,避免被觀察
    // 猜想可能是之後觀察者進行監視的時候會忽略掉有這個標記的對象
    // 具體原因待以後分析
    // a flag to avoid this being observed
    vm._isVue = true
    // 合併options對象
    // 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 {
      // 否則執行合併options函數,並賦值給vm的公共屬性
      // 在這裏的合併函數主要是解決與繼承自父類的配置對象的合併
      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)
    // 調用生命週期鉤子函數beforeCreate
    callHook(vm, 'beforeCreate')
    // 初始化父組件注入屬性
    initInjections(vm) // resolve injections before data/props
    // 初始化狀態相關屬性和功能
    initState(vm)
    // 初始化子組件屬性提供器
    initProvide(vm) // resolve provide after data/props
    // 調用生命週期鉤子函數created
    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)
    }

    // 執行DOM元素掛載函數
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

還記得在文件組織裏分析的,Component類的的具體定義可參照這個文件

初始化函數內容不多,主要做了這麼幾件事:

  • 整理options配置對象
  • 開始進入Vue實例的生命週期進程,並在生命週期相應階段初始化實例屬性和方法
  • 將初始化好的對象掛載到Dom元素上,繼續生命週期的運行

這部分代碼已經完整地展示出了將Vue實例對象掛載到DOM元素上並執行渲染的大半程生命週期的進程,在此之後就是視圖的交互過程,直到實例對象被銷燬。後半段代碼清晰地呈現了生命週期中各個功能的初始化順序,也就是那張著名的生命週期圖示的對應代碼。

各個生命週期的初始化函數內容比較豐富,決定在另一個文檔中做一個單獨討論類初始化函數詳情


雖然核心類的定義代碼寥寥數行,但是在類初始化的過程中執行了非常多的其他功能的初始化,從這個基礎的類的實現去一步步解開每一個更復雜的功能的實現可能會讓學習者能逐步深入瞭解Vue的豐富內容,基於源代碼一句句的解釋雖然非常冗餘,但是希望即便是基礎不是特別紮實的同學也能看懂,認識到源碼學習不再是大難題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章