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