本篇代碼位於vue/src/core/global-api/vue
Vue暴露了一些全局API來強化功能開發,API的使用示例官網上都有說明,無需多言。這裏主要來看一下全局API模塊的實現。全局API的文件夾裏有一個入口文件,各個功能分開定義,在這個入口文件中統一注入。ios
/* @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) }
入口文件從整體來說能夠分爲兩個部分:git
定義靜態屬性github
config
:在最開始的部分定義了Vue的靜態屬性 config
,這是全局配置對象。options
:稍後定義的 options
對象是很是重要的屬性,存放初始化的數據,咱們平時在建立Vue實例時傳入的配置對象最終要與這份配置屬性合併,在實例初始化函數中的合併配置對象一部分能夠初窺端倪。定義靜態方法api
util
:雖然暴露了一些輔助方法,但官方並不將它們列入公共API中,不鼓勵外部使用。set
:設置響應式對象的響應式屬性,強制觸發視圖更新,在數組更新中很是實用,不適用於根數據屬性。delete
:刪除響應式屬性強制觸發視圖更新, 使用情境較少。nextTick
:結束此輪循環後執行回調,經常使用於須要等待DOM更新或加載完成後執行的功能。use
:安裝插件,自帶規避重複安裝。mixin
:經常使用於混入插件功能,不推薦在應用代碼中使用。extend
:建立基於Vue的子類並擴展初始內容。directive
:註冊全局指令。component
:註冊全局組件。filter
:註冊全局過濾器。除了後6個方法以外,其餘的輔助函數和方法都已經在其餘模塊裏見識過了,繼續來詳細探索一下剩下的6個功能。initAssetRegisters
方法爲Vue類註冊的全局函數包括了 directive
、component
、filter
,三個方法合在一個模塊裏,其他都分了各自的模塊來定義。數組
// 導入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爲了統一插件定義規範所設置的入口方法名稱。緩存
// 導入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屬性。因爲使用場景大都是用來混入插件功能的,因此建立項目時幾乎沒有運用,瞭解便可。app
// 導入資源類型,模塊方法和輔助方法 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。另外對繼承的父類的屬性也進行了引用存儲。less
// 導入資源類型和輔助函數 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類,指令是格式化成定義對象,方便以後對指令的統一處理。ide
全局API的細節大概就是以上這些,對於常常使用的方式,瞭解其具體實現能夠幫助咱們在應用時避免出現沒必要要的錯誤,對於不常常使用的方法,在探索其實現時能夠學習它們的實現原理和良好的方式。重要是在實踐中分清楚每個方法的使用場景,選取最恰當的方式實現功能。