Vue源碼探究-全局API

Vue源碼探究-全局API

本篇代碼位於vue/src/core/global-api/

Vue暴露了一些全局API來強化功能開發,API的使用示例官網上都有說明,無需多言。這裏主要來看一下全局API模塊的實現。全局API的文件夾裏有一個入口文件,各個功能分開定義,在這個入口文件中統一注入。

入口文件index.js

/* @flow */
// 從各個模塊導入功能函數
import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'

// 導入內部輔助函數
import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

// 定義並導出initGlobalAPI函數
export function initGlobalAPI (Vue: GlobalAPI) {
  // 定義全局配置對象
  // config
  const configDef = {}
  // 定義配置對象的取值器函數
  configDef.get = () => config
  // 不允許在外部修改配置對象,非生產環境會給出警告
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  // 定義Vue類的靜態屬性config
  Object.defineProperty(Vue, 'config', configDef)

  // 暴露工具方法
  // exposed util methods.
  // 注意:這不是全局公共API的一部分,
  // 除非瞭解到它們會帶來的風險否則請避免使用。
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  // 定義Vue的靜態方法set、delete、nextTick
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 初始化Vue.options屬性爲空對象
  Vue.options = Object.create(null)
  // 初始化options屬性的各個子屬性爲空對象
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // 這用於標識“基礎”構造函數
  // 以在Weex的多實例場景中擴展所有普通對象組件
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  // 擴展options.components屬性,加入內建組件
  extend(Vue.options.components, builtInComponents)

  // 向Vue類掛載靜態方法
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

入口文件從總體來講可以分爲兩個部分:

  • 定義靜態屬性

    • config:在最開始的部分定義了Vue的靜態屬性 config,這是全局配置對象。
    • options:稍後定義的 options 對象是非常重要的屬性,存放初始化的數據,我們平時在創建Vue實例時傳入的配置對象最終要與這份配置屬性合併,在實例初始化函數中的合併配置對象一部分可以初窺端倪。
  • 定義靜態方法

    • util:雖然暴露了一些輔助方法,但官方並不將它們列入公共API中,不鼓勵外部使用。
    • set:設置響應式對象的響應式屬性,強制觸發視圖更新,在數組更新中非常實用,不適用於根數據屬性。
    • delete:刪除響應式屬性強制觸發視圖更新, 使用情境較少。
    • nextTick:結束此輪循環後執行回調,常用於需要等待DOM更新或加載完成後執行的功能。
    • use:安裝插件,自帶規避重複安裝。
    • mixin:常用於混入插件功能,不推薦在應用代碼中使用。
    • extend:創建基於Vue的子類並擴展初始內容。
    • directive:註冊全局指令。
    • component:註冊全局組件。
    • filter:註冊全局過濾器。

除了後6個方法之外,其他的輔助函數和方法都已經在其他模塊裏見識過了,繼續來詳細探索一下剩下的6個功能。initAssetRegisters 方法爲Vue類註冊的全局函數包括了 directivecomponentfilter,三個方法合在一個模塊裏,其餘都分了各自的模塊來定義。

全局API use

// 導入toArray輔助函數
import { toArray } from '../util/index'

// 定義並導出initUse函數
export function initUse (Vue: GlobalAPI) {
  // 定義Vue類靜態方法use,接受插件函數或對象
  Vue.use = function (plugin: Function | Object) {
    // 定義內部屬性installedPlugins,存放已安裝插件
    // 首次應用時定義爲空數組
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 檢測是否安裝過傳入的插件,已存在則返回
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // 處理附加參數,加入參數Vue
    // additional parameters
    // 將傳入的參數轉化爲數組
    const args = toArray(arguments, 1)
    // 插入Vue類本身爲第一個元素
    args.unshift(this)
    // 如果插件有install方法,則在plugin對象上調用並傳入新參數
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // 如果plugin本身是函數,則直接調用並傳入新參數
      plugin.apply(null, args)
    }
    // 向緩存插件數組中添加此插件並返回
    installedPlugins.push(plugin)
    return this
  }
}

use 方法的實現很簡單,在內部定義了數組來緩存已經註冊過的插件,並在下一次註冊前檢驗是否已註冊過,可以避免重複註冊插件。接受的參數值得注意,如果插件本身就是一個函數,則直接調用;如果插件是對象,則必須有install方法,否則沒有任何行爲,這是Vue爲了統一插件定義規範所設置的入口方法名稱。

全局API mixin

// 導入mergeOptions輔助函數
import { mergeOptions } from '../util/index'

// 定義並導出initMixin函數
export function initMixin (Vue: GlobalAPI) {
  // 定義Vue的靜態方法mixin
  Vue.mixin = function (mixin: Object) {
    // 合併配置對象,重置Vue類的靜態屬性options
    this.options = mergeOptions(this.options, mixin)
    // 返回
    return this
  }
}

mixin 方法的實現更加簡潔,在重用Vue類的所有狀態下,只是重新合併了options屬性。由於使用場景大都是用來混入插件功能的,所以創建項目時幾乎沒有運用,瞭解即可。

全局API extend

// 導入資源類型,模塊方法和輔助方法
import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

// 定義並導出initExtend
export function initExtend (Vue: GlobalAPI) {
  // 每個實例構造函數,包括Vue都有唯一的cid。
  // 這使我們能夠爲原型繼承創建包裝的“子構造函數”並緩存它們。
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  // 設置Vue的cid爲0
  Vue.cid = 0
  // 定義cid變量
  let cid = 1

  // 定義類繼承方法
  /**
   * Class inheritance
   */
  // 定義Vue類靜態方法extend,接受擴展選項對象
  Vue.extend = function (extendOptions: Object): Function {
    // extendOptions若未定義則設置爲空對象
    extendOptions = extendOptions || {}
    // 存儲父類和父類的cid
    const Super = this
    const SuperId = Super.cid
    // 定義緩存構造器對象,如果擴展選項的_Ctor屬性未定義則賦值空對象
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // 如果緩存構造器已存有該構造器,則直接返回
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // 獲取擴展配置對象名稱或父級配置對象名稱屬性,賦值給name
    const name = extendOptions.name || Super.options.name
    // 在非生產環境下驗證name是否合法並給出警告
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    // 定義子類構造函數
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 實現子類原型繼承,原型指向父類原型,構造器指向Sub
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    // 定義子類cid,並遞增cid
    Sub.cid = cid++
    // 定義子類options屬性,合併配置對象
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    // 定義子類super屬性,指向父類
    Sub['super'] = Super

    // 對於props和computed屬性,擴展時在Vue實例上定義了代理getter。
    // 這避免了對每個創建的實例執行Object.defineProperty調用。
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 初始化子類的props
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 初始化子類的計算屬性
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // 定義子類的全局API,擴展、混入和使用插件
    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // 創建子類的資源註冊方法,允許子類有私有資源
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // 啓用遞歸自查找
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // 在擴展時保持對父類配置對象的引用,
    // 以後實例化時可以檢查父級配置對象是否更新
    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // 緩存子類構造函數
    // cache constructor
    cachedCtors[SuperId] = Sub
    // 返回
    return Sub
  }
}

// 定義初始化propss函數
function initProps (Comp) {
  // 獲取配置對象的props屬性
  const props = Comp.options.props
  // 設置代理
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

// 定義初始化計算屬性函數
function initComputed (Comp) {
  // 獲取配置對象的computed屬性
  const computed = Comp.options.computed
  // 設置代理
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

extend 方法是最爲複雜的全局API了,它在擴展類實現繼承時進行了很多處理:除去判斷是否有已存儲的子類構造函數之外,首先是實現類繼承,原理是原型式繼承;然後爲子類初始化props和computed屬性的代理:最後是擴展全局API。另外對繼承的父類的屬性也進行了引用存儲。

全局API 資源獲取和註冊

// 導入資源類型和輔助函數
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

// 定義並註冊initAssetRegisters函數
export function initAssetRegisters (Vue: GlobalAPI) {
  // 創建資源註冊方法
  /**
   * Create asset registration methods.
   */
  // 遍歷ASSET_TYPES數組,爲Vue定義相應方法
  // ASSET_TYPES包括了directive、 component、filter
  ASSET_TYPES.forEach(type => {
    // 定義資源註冊方法,參數是標識名稱id,和定義函數或對象
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // 如果未傳入definition,則視爲獲取該資源並返回
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        // 否則視爲註冊資源
        // 非生產環境下給出檢驗組件名稱的錯誤警告
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        // 如果是註冊component,並且definition是對象類型
        if (type === 'component' && isPlainObject(definition)) {
          // 設置definition.name屬性
          definition.name = definition.name || id
          // 調用Vue.extend擴展定義,並重新賦值
          definition = this.options._base.extend(definition)
        }
        // 如果是註冊directive且definition爲函數
        if (type === 'directive' && typeof definition === 'function') {
          // 重新定義definition爲格式化的對象
          definition = { bind: definition, update: definition }
        }
        // 存儲資源並賦值
        this.options[type + 's'][id] = definition
        // 返回definition
        return definition
      }
    }
  })
}

initAssetRegisters 包含有三,分別是 directivecomponentfilter 的註冊並獲取方法。方法的作用視參數而定,只傳入資源標識名稱ID未傳定義函數或對象,則視爲獲取資源方法,如果都傳則是資源註冊方法,可謂是非常js化的。比較重要的是這裏對於 definition 參數的重賦值,根據資源的種類不同,會進行不同的處理:組件主要是擴展Vue類,指令是格式化成定義對象,方便之後對指令的統一處理。


全局API的細節大概就是以上這些,對於經常使用的方式,瞭解其具體實現可以幫助我們在應用時避免出現不必要的錯誤,對於不經常使用的方法,在探索其實現時可以學習它們的實現原理和良好的方式。重要是在實踐中分清楚每一個方法的使用場景,選取最恰當的方式實現功能。

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