vue2源碼解析之全局API實例詳解
前言
實例方法是掛載在vue的原型上,而全局方法是掛載在vue上;全局方法有12個,分別為extend、nextTick、set、delete、directive、filter、component、use、mixin、version和observable;
Vue.extend()
基本使用
/* 作用: 使用基礎vue構造器,創(chuàng)建一個子類,參數(shù)是一個包含組件選項的對象 `data` 選項是特例,需要注意 - 在 `Vue.extend()` 中它必須是函數(shù) */ Vue.extend(options) // eg: <div id="mount-point"></div> // 創(chuàng)建構造器 var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 創(chuàng)建 Profile 實例,并掛載到一個元素上。 new Profile().$mount('#mount-point') // 結果: <p>Walter White aka Heisenberg</p>
整體源碼
// 源碼位置 src/core/global-api/extend.js Vue.extend = function (extendOptions: Object): Function { // 初始化參數(shù) extendOptions = extendOptions || {} // 存儲父類 const Super = this // 存儲父類的唯一標識 const SuperId = Super.cid // 獲取到參數(shù)中的緩存數(shù)據(jù) const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) // 如果已經(jīng)緩存過就會緩存中獲取返回 if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } // 獲取到name const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } // 創(chuàng)建一個子類 const Sub = function VueComponent (options) { this._init(options) } // 原型繼承 Sub.prototype = Object.create(Super.prototype) // 修改構造器 Sub.prototype.constructor = Sub // 子類的唯一標識 Sub.cid = cid++ // 合并父類和傳遞進來的參數(shù) Sub.options = mergeOptions( Super.options, extendOptions ) // 子類中存儲父類 Sub['super'] = Super // 如果子類中有props 進行初始化 if (Sub.options.props) { initProps(Sub) } // 如果子類中有計算屬性,進行初始化 if (Sub.options.computed) { initComputed(Sub) } // 把父類的extend,mixin,use放在子類上 Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // 把指定的屬性全部從父類上拷貝到子類上 ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) if (name) { Sub.options.components[name] = Sub } Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor // 緩存子類 cachedCtors[SuperId] = Sub return Sub } } // 把子類中的props存儲到原型上 function initProps (Comp) { const props = Comp.options.props for (const key in props) { proxy(Comp.prototype, `_props`, key) } } // 把子類中的計算屬性存儲到原型上 function initComputed (Comp) { const computed = Comp.options.computed for (const key in computed) { defineComputed(Comp.prototype, key, computed[key]) } }
extend方法內(nèi)部其實就是創(chuàng)建一個子類,通過原型繼承的方式繼承父類,然后把父類的屬性和一些方法存儲到子類上,并且把子類進行緩存,再一開始就先從緩存中獲取子類,如果沒有就進行屬性和方法的繼承,最后返回子類;
Vue.nextTick,Vue.set,Vue.delete
以上三種都在實例方法中解析過
Vue.directive、Vue.filter、Vue.component
基本使用
注冊或獲取全局指令
// 注冊 Vue.directive('my-directive', { bind: function () {}, inserted: function () {}, update: function () {}, componentUpdated: function () {}, unbind: function () {} }) // 注冊 (指令函數(shù)) Vue.directive('my-directive', function () { // 這里將會被 `bind` 和 `update` 調用 }) // getter,返回已注冊的指令 var myDirective = Vue.directive('my-directive')
注冊或獲取過濾器
// 注冊 Vue.filter('my-filter',function(){}) // 獲取 Vue.filter('my-filter')
注冊或獲取組件
// 注冊 傳入一個擴展過的構造器 Vue.component('my-component', Vue.extend({})) // 注冊 傳入一個選項對象 Vue.component('my-component', {}) // 獲取 Vue.component('my-component')
源碼分析
// 源碼位置 src/core/global-api/ export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. */ ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { // 不存在definition 表示獲取,直接從options下的相應的typs+'s'進行獲取 if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } // 如果是組件 并且definition為一個{}對象,那么就通過extend繼承vue if (type === 'component' && isPlainObject(definition)) { // 沒有name就使用id definition.name = definition.name || id // 使definition繼承vue definition = this.options._base.extend(definition) } // 如果是指令并且definition是一個函數(shù),那么就把這個函數(shù)作為bind和update if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // 設置值 保存到對應的key下 this.options[type + 's'][id] = definition return definition } } }) }
指令,過濾器,組件把它們放在一個數(shù)組中,遍歷這個數(shù)組,分別進行處理
export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ){} })
如果沒有傳遞第二個參數(shù),表示是獲取,直接進行獲取返回
if (!definition) { return this.options[type + 's'][id] }
否則就是傳遞了第二個參數(shù),表示是注冊,首先判斷是否是組件,是組件并且第二個參數(shù)是對象,那么就繼承Vue;
// 如果是組件 if (type === 'component' && isPlainObject(definition)) { // 沒有name就使用id definition.name = definition.name || id // 使definition繼承vue definition = this.options._base.extend(definition) }
如果是指令,并且第二個參數(shù)是函數(shù),那么直接放在一個對象中
// 如果是指令 if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } }
最后給相應的對象下的自定義指令或過濾器或組件進行賦值并返回
// 設置值 this.options[type + 's'][id] = definition return definition
通過以上三種方法就可以自定義組件,過濾器和指令,然后就可以在模板中使用它們,最終的處理還是在模板編譯節(jié)點進行的;
Vue.use
基本使用
/* 作用: 安裝 Vue.js 插件。如果插件是一個對象,必須提供 `install` 方法。 如果插件是一個函數(shù),它會被作為 install 方法。install 方法調用時,會將 Vue 作為參數(shù)傳入。 該方法需要在調用 `new Vue()` 之前被調用。 當 install 方法被同一個插件多次調用,插件將只會被安裝一次 plugin:- `{Object | Function} plugin` */ Vue.use( plugin )
源碼預覽
// 源碼位置 src/core/global-api/use.js export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { // 初始_installedPlugins變量 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // 如果已經(jīng)存在直接返回 if (installedPlugins.indexOf(plugin) > -1) { return this } // 獲取剩余的參數(shù)轉成數(shù)組 const args = toArray(arguments, 1) // 把this添加進去 args.unshift(this) // 如果install是一個函數(shù) 直接執(zhí)行 if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // 如果插件是一個函數(shù),直接執(zhí)行 plugin.apply(null, args) } // 添加到數(shù)組中 installedPlugins.push(plugin) return this } }
首先獲取到用來緩存的屬性,如果不存在初始化為一個數(shù)組,再判斷傳遞進來的插件是否已經(jīng)存在,存在表示重復操作,直接返回;(實現(xiàn):當 install 方法被同一個插件多次調用,插件將只會被安裝一次)
// 初始_installedPlugins變量 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // 如果已經(jīng)存在直接返回 if (installedPlugins.indexOf(plugin) > -1) { return this }
接著獲取到剩余的參數(shù),并且把當前實例也作為參數(shù)(實現(xiàn):install 方法調用時,會將 Vue 作為參數(shù)傳入。)
// 獲取剩余的參數(shù)轉成數(shù)組 const args = toArray(arguments, 1) // 把this添加進去 args.unshift(this)
判斷plugin.install是否是一個函數(shù),如果是函數(shù)就通過plugin執(zhí)行(實現(xiàn):如果插件是一個對象,必須提供 install
方法)
// 如果install是一個函數(shù) 直接執(zhí)行 if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) }
否則plugin是一個函數(shù),那么直接執(zhí)行(實現(xiàn):如果插件是一個函數(shù),它會被作為 install 方法)
else if (typeof plugin === 'function') { // 如果插件是一個函數(shù),直接執(zhí)行 plugin.apply(null, args) }
最后添加到緩存中并返回
// 添加到數(shù)組中 installedPlugins.push(plugin) return this
Vue.mixin
基本使用
/* 作用: 全局注冊一個混入,影響注冊之后所有創(chuàng)建的每個vue實例,插件作者可以使用混入, 向組件注入自定義的行為。**不推薦在應用代碼中使用**。 */ Vue.mixin()
源碼預覽
// 源碼位置 src/core/global-api/mixin.js export function initMixin (Vue: GlobalAPI) { Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin) return this } }
混入很簡單,就是把傳遞進來的數(shù)據(jù)和實例上已有的數(shù)據(jù)進行合并替換,最后重新賦值給實例上的options;
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } // 設置props,把帶有-的屬性名改成駝峰,存到一個新的對象中進行返回 normalizeProps(child, vm) // 設置注入的屬性 把inject中的數(shù)據(jù)都轉成 {from:val}形式的對象返回 normalizeInject(child, vm) // 設置指令 返回指令的值為{ bind: def, update: def }的形式 normalizeDirectives(child) // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. // 遞歸處理extends和mixins if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } // 創(chuàng)建一個新的對象 const options = {} let key for (key in parent) { // 遍歷父級進行設置屬性 mergeField(key) } for (key in child) { // 遍歷子級進行設置屬性 if (!hasOwn(parent, key)) { // 父級不存在此屬性就進行設置 mergeField(key) } } // 通過策略模式給不同的屬性設置對應的執(zhí)行函數(shù), function mergeField (key) { // 獲取到不同屬性的函數(shù) const strat = strats[key] || defaultStrat // 執(zhí)行對應的函數(shù)重新設置屬性 options[key] = strat(parent[key], child[key], vm, key) } return options }
把子級的屬性進行單獨處理,處理完成之后和父級的屬性進行合并,合并之后進行返回;
Vue.compile
基本使用
/* 作用: 將一個模板字符串編譯成 render 函數(shù)。**只在完整版時可用**。 template: 字符串模板 */ Vue.compile(template)
源碼預覽
Vue.compile = compileToFunctions
compileToFunctions函數(shù)在模板編譯節(jié)點已經(jīng)解析完
Vue.observable
基本用法
/* 作用: 讓一個對象可響應,Vue內(nèi)部會用它來處理data函數(shù)返回的對象 返回的對象可以直接用于[渲染函數(shù)]和[計算屬性]內(nèi),并且會在發(fā)生變更時觸發(fā)相應的更新。 也可以作為最小化的跨組件狀態(tài)存儲器,用于簡單的場景: */ const state = Vue.observable({ count: 0 }) const Demo = { render(h) { return h('button', { on: { click: () => { state.count++ }} }, `count is: ${state.count}`) } }
源碼預覽
// 源碼位置 src/core/global-api/ // 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj }
總結
extend: 內(nèi)部實現(xiàn)一個子類,通過原型繼承的方式繼承自當前調用者(父類),并且把父類中的屬性進行合并,存儲到自己的options中,父類的方法添加到自己構造器上;把子類進行緩存;一進入到函數(shù)內(nèi)部首先從緩存中獲取,沒有才進行創(chuàng)建子類;
directive,filter,component: 這三種方式通過一個函數(shù)實現(xiàn),首先判斷有沒有傳遞第二個參數(shù),如果沒傳遞表示是獲取,直接獲取返回;如果有傳遞表示注冊,如果是指令并且第二個參數(shù)是函數(shù),那么把函數(shù)作為bind和update的值;如果是組件并且第二個參數(shù)是函數(shù),那么通過extend方法進行繼承;
use: 如果緩存中有此插件就直接返回,否則就判斷傳遞進來的參數(shù)是對象還是函數(shù),如果是對象那么就執(zhí)行對象上的install方法,如果是函數(shù)直接執(zhí)行;并且把當前實例作為參數(shù)傳遞進去;最后把插件進行緩存;
mixin: 首先把props,inject和指令的屬性進行統(tǒng)一格式化操作;然后把當前實例的options和傳遞進來的參數(shù)進行合并,相同屬性傳遞進來的參數(shù)會覆蓋options中的屬性;
observable: 通過調用observe給當前傳遞進來的對象添加響應式,并且返回該對象;
到此這篇關于vue2源碼解析之全局API實例詳解的文章就介紹到這了,更多相關vue2全局API內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue3中實現(xiàn)拖拽排序代碼示例(vue-draggable-next的使用)
在Vue3中使用拖拽功能時應選用vue-draggable-next插件,傳統(tǒng)的draggable插件不兼容Vue3,可能導致TypeError錯誤,安裝后,需在項目中引入并使用,具體步驟包括安裝插件、引入使用、查看效果和相關說明,需要的朋友可以參考下2024-09-09vue.js內(nèi)部自定義指令與全局自定義指令的實現(xiàn)詳解(利用directive)
這篇文章主要給大家介紹了關于vue.js內(nèi)部自定義指令與全局自定義指令的實現(xiàn)方法,vue.js中實現(xiàn)自定義指令的主要是利用directive,directive這個單詞是我們寫自定義指令的關鍵字,需要的朋友們下面跟著小編來一起學習學習吧。2017-07-07