Vue.extend做爲一個全局api,固然值得咱們去深刻學習的,同時也是實現編程式組件的重要途徑,因此咱們經過源碼導讀加實踐的方式開始吧。首先咱們會帶着幾個問題來進行學習,若是你都不會,哈哈哈恭喜你,學完本篇你就會明白了。html
{Object} optionsvue
使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。node
data 選項是特例,須要注意 - 在 Vue.extend() 中它必須是函數。編程
<div id="mount-point"></div>
複製代碼
// 建立構造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 建立 Profile 實例,並掛載到一個元素上。
new Profile().$mount('#mount-point')
複製代碼
結果以下:element-ui
<p>Walter White aka Heisenberg</p>
複製代碼
在咱們賞析源碼以前,咱們首先確定要找到這個api及調用的位置,這樣才能更好的理解它的做用。經過官方用法咱們就知道,它確定和組件有關係,那麼咱們很容易找到在源碼中建立組件的位置,接下來咱們一步一步找:api
vnode = createComponent(Ctor, data, context, children, tag)
複製代碼
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
複製代碼
這裏, baseCtor其實就是Vue,具體緣由在之後分析組件源碼會講解,這裏不做研究。咱們終於找到了Vue.extend調用的位置,就是在Vue建立每一個組件的時候調用,經過構造器建立子類。下面就是完整的源碼:緩存
Vue.cid = 0
let cid = 1
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 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.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// 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 } } 複製代碼
接下來咱們就一步一步完全搞懂源碼。bash
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
複製代碼
首先,extendOptions使咱們傳入進去的模板,這裏面的this就是調用extend的對象,就是Vue,而後保存在變量Super中,變量SuperId就保存Vue中的惟一標識(每一個實例都有本身惟一的cid).app
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
複製代碼
這一段做爲緩存策略的,放在下面說。dom
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
複製代碼
用一個name變量來保存組件的名字,就是我寫組件時候的name,若是沒寫就使用父組件的name,而後對name經過validateComponentName函數驗證,主要就是判斷name不能是html元素和不能是非法命名。
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
複製代碼
上面咱們建立一個子類Sub,這裏咱們經過繼承,使Sub擁有了Vue的能力,而且添加了惟一id(每一個組件的惟一標識符)
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
複製代碼
這裏調用了mergeOptions函數實現了父類選項與子類選項的合併,而且子類的super屬性指向了父類。
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
複製代碼
初始化了props和computed.
// 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
}
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
複製代碼
將父類的方法複製到子類,包括extend,mixin,use,component,directive,filter.還新增屬性superOptions,extendOptions,sealedOptions 。
// cache constructor
cachedCtors[SuperId] = Sub
複製代碼
以前的代碼結合,將父類的id保存在子類的屬性上,屬性值爲子類,在以前會進行判斷若是構造過子類,就直接將父類保存過的id值給返回了,就是子類自己不須要從新初始化,,做爲一個緩存策略。
整體來講,其實就是建立一個Sub函數並繼承了父級。
咱們在通常調用組件時,都會先組件註冊,再在模板中引用。一個兩個仍是能夠接受的,那麼若是這個組件不少不少呢,因此每次註冊應用就顯得力不從心了。用過element-ui咱們都知道,能夠不用註冊,直接經過命令調用:
this.$message.success('成功了')
複製代碼
是否是特別特別方便,簡單明瞭,並且還符合編程思惟。接下來咱們將手動實現一下:
<template>
<div class='toast'
v-show='isShow'>
{{message}}
</div>
</template>
<script>
export default {
data () {
return {
message: '',
isShow: false
}
},
methods: {
show (message, duration) {
this.message = message
this.isShow = true
setTimeout(() => {
this.isShow = false
this.message = ''
}, duration)
}
}
}
</script>
<style scoped>
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999;
padding: 8px 10px;
background-color: rgba(0, 0, 0, 0.3);
}
</style>
複製代碼
這個組件很是簡單,兩個變量分別是顯示的消息和是否顯示。我還定義了一個方法,做用就是使模板取消隱藏,而且傳入一個duration參數經過定時器表示顯示的時間,時間一到isShow就變false,能夠說是很是簡單了,模板有了,下面開始重點了。
import Toast from './toast'
const obj = {}
obj.install = function (Vue) {
// 建立構造器
const ToastContrystor = Vue.extend(Toast)
// new的方式 根據組件構造器,能夠建立組件對象
const toast = new ToastContrystor()
// 手動掛載某一個元素上
toast.$mount(document.createElement('div'))
// toast.$el對應的就是div
document.body.appendChild(toast.$el)
//組件掛載到Vue原型上
Vue.prototype.$toast = toast
}
export default obj
複製代碼
把組件看成插件同樣,經過Vue.use()使用,註釋都寫的很是清楚了,一步一步理解。
import Vue from 'vue'
import toast from '@/components/common/toast/index.js'
Vue.use(toast)
複製代碼
this.$toast.error('註冊失敗,請從新輸入', 1000)
複製代碼
就能夠在項目各個地方調用了。咱們就實現了一個編程式的組件。
VUe.extend整體來講其實就是建立一個類來繼承了父級,頂級必定是Vue.這個類就表示一個組件,咱們能夠經過new的方式來建立。學習了extend咱們就很容易的實現一個編程式組件。