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咱们就很容易的实现一个编程式组件。