在Vue初始化中,$option属性来自于mergeOptions函数:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
它接受3个参数,其中后面两个很好理解:一个是new Vue(options)
时传进来的参数options
;一个是初始化最开始就定义的变量const vm: Component = this
,它指向了该Vue
实例。那么这第一个参数是个啥玩意?这个函数返回了啥东西?
resolveConstructorOptions
这个函数源码在src/core/instance/init.js
中的96行:
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
这个函数接受一个参数,就是实例的构造函数,即vm.constructor。通过上面的代码可以知道,他的实现逻辑分为两种,一种是构造函数上的super属性存在,则对其进行处理;如果super属性不存在,则直接返回构造函数上的option值。
super不存在
我们通过前面的初始化分析里可知,当new Vue时,vm.constructor里是不存在super属性的。这时候resolveConstructorOptions是直接返回的构造函数的option,还是通过上文的分析知道,这个构造函数的option是在initGlobalAPI里实现的:
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
首先,我们把基础的构造函数(即new Vue里调用的那个)指定到options里的_base属性上,然后再加上ASSET_TYPES里的三个属性。
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
最后一步,通过extend
方法,把全局组件KeepAlive
加入到options里的components上。
等等,这还没完。在前面的初始化分析里,我们看到在platforms/web/runtime/index
里,我们还会安装平台相关的指令和组件。
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
在web端我们安装的是v-model
、v-show
这两个指令和Transition
、TransitionGroup
这两个组件。
因此,在super不存在的情况下,我们return的option是这个样子的:
super存在
那么super在什么时候会存在呢?因为这resolveConstructorOptions
是被export出去的,也就是说不只在init.js
里使用了他。我们全局搜索一下resolveConstructorOptions
,发现他在src/core/vdom/create-component.js
也有用到:
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
......
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
......
}
也就是说我们在createComponent
,即构造一个组件实例的时候,也会调用这个resolveConstructorOptions
,这时候函数接收的Ctor
来自于Ctor = baseCtor.extend(Ctor)
,而这个extend就是初始化时挂载到Vue构造函数上的extend方法:
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
......
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
......
return Sub
}
那么这个extend方法他到底做了啥?其实官网中已经做了解释:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
所以extend函数的作用就是返回一个子组件的构造函数,他通过Object.create(), prototype, constructor三者结合实现了子组件对基础Vue组件的继承。我们来看一下具体的流程:
- 通过
const Super = this
,把Super指向父类,即基础Vue构造函数。 - 定义了子组件的构造函数Sub。当我们new这个子组件时,就会执行这个构造函数Sub,而他最终执行的是
this._init(options)
。当然,这时候我们的Sub同志还是一个光杆司令,只有原生function对象的属性和方法,当然不会有_init
。为了让Sub拥有_init
方法,我们就开始了从Super上的继承。 Sub.prototype = Object.create(Super.prototype)
,通过Object.create方法,将Super的原型作为自己原型的__proto__,实现原型继承。这时候,我们的Sub就会拥有Vue基础构造器的所有原型方法,包括Vue.prototype._init
。- 当然,这时候还有个小问题,Sub.prototype.constructor指向的也是Super.prototype.constructor,也就是Vue基础构造函数function Vue()。这时候需要通过
Sub.prototype.constructor = Sub
把他指向我们子类自己的构造函数function VueComponent()
然后通过mergeOption等一系列骚操作完成子组件独有属性的挂载,最后返回了这个子类构造函数Sub。什么?为啥不用继承?当然是为了避免跟全局的污染啊,继承的其实都是公共的属性和方法,像data这种的当然不能通过继承实现。
那么,我们心心念念的super属性呢?其实就在这段代码里 Sub['super'] = Super
,通过他把Vue基础构造器挂载到了子类构造函数的super属性上了。同时我们也通过Sub.superOptions = Super.options
把Vue基础构造器的option选项挂载到了子类构造函数的superOptions属性上。
既然找到了super,那么Ctor = baseCtor.extend(Ctor)
的这个Ctor中自然会有super属性,因此这时候执行的 resolveConstructorOptions(Ctor)
就会走另外一段逻辑:
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super);
const cachedSuperOptions = Ctor.superOptions;
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions;
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor);
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions);
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
if (options.name) {
options.components[options.name] = Ctor;
}
}
}
- 首先调用resolveConstructorOptions方法,把父类的options找出来,因为可能父类也有父类,因此这里是递归查找。他从最开始不存在super的基础构造函数function Vue()开始,进行递归,把上一级父类上最新的options更新到变量superOptions中。
- 然后把上面提到的,extend时父类的options,即
Ctor.superOptions
赋值给cachedSuperOptions变量。 - 如果当这两个变量值不等时,就说明在extend之后,我们的父组件options悄悄咪咪的发生了变化,比如官网中这样比较粗暴的全局混入:
- 那么既然父类可能被混入,我们这个子类当然也可能咯!我们通过
resolveModifiedOptions
这个函数,来判断现有的子类option和在extend中挂载到子类option中的sealedOptions
(可以查看上面extend函数最后三段代码)是否一样,如果不一样说明肯定有什么骚操作在extend之后改变了子类option。 - 然后如果,子类自己或者父类在extend后options有变化,会通过
Ctor.superOptions = superOptions;
和extend(Ctor.extendOptions, modifiedOptions);
更新最新的值到superOptions和extendOptions - 更新完后,最新的options值通过
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
来获得。
通过上面的操作,可以确保resolveConstructorOptions
返回的都是最新的Ctor.options
值。
存在super的情况比较复杂,我们用一个简单的例子来拆解一下:
let Score = Vue.extend({
template: '<p>{{firstName}} {{lastName}} : {{score}}</p>'
});
Vue.mixin({
data() {
return {
firstName: 'Walter',
lastName: 'White'
};
}
});
Score.mixin({
data: function () {
return {
score: '99'
};
}
});
new Score().$mount('#demo');
- 首先,因为Score是通过Vue.extend构造的子类,因此他的Ctor存在super属性
- 然后,在extend的时候,父类Vue中时没有data属性的,即
cachedSuperOptions
中没有data
但是经过Vue.mixin的骚操作后,最新得到的superOptions
是有data的
这时候因为两者不相等,就会进入if语句里面。
- 又因为Socre.mixin的骚操作后,
resolveModifiedOptions
这个函数发现Socre中的options又比sealedOptions
多了data
这时候的modifiedOptions
就不是undefined了:
4.最后,执行 options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
,最新的options值通过mergeOptions这个函数得到
所以,总结一下resolveConstructorOptions函数的作用:返回类的构造函数上最新的options值。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。