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類註冊的全局函數包括了 directive
、component
、filter
,三個方法合在一個模塊裏,其餘都分了各自的模塊來定義。
全局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
包含有三,分別是 directive
、component
、filter
的註冊並獲取方法。方法的作用視參數而定,只傳入資源標識名稱ID未傳定義函數或對象,則視爲獲取資源方法,如果都傳則是資源註冊方法,可謂是非常js化的。比較重要的是這裏對於 definition
參數的重賦值,根據資源的種類不同,會進行不同的處理:組件主要是擴展Vue類,指令是格式化成定義對象,方便之後對指令的統一處理。
全局API的細節大概就是以上這些,對於經常使用的方式,瞭解其具體實現可以幫助我們在應用時避免出現不必要的錯誤,對於不經常使用的方法,在探索其實現時可以學習它們的實現原理和良好的方式。重要是在實踐中分清楚每一個方法的使用場景,選取最恰當的方式實現功能。