Vue原始碼探究-核心類的實現

ushio發表於2018-09-28

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的豐富內容,基於原始碼一句句的解釋雖然非常冗餘,但是希望即便是基礎不是特別紮實的同學也能看懂,認識到原始碼學習不再是大難題。

相關文章