resolveConstructorOptions
mergeOptions
方法的调用在_init
中,我们从之前了解到_init
是在new Vue
时,这个一般发生在两个地方:
- 外部代码,即我们自己
new Vue
; - 子组件内部调用
new Vue
。
Vue.prototype._init = function (options?: Object) {
// 如果是子组件
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
// 否则
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
// ...
}
我们这次先来学习resolveConstructorOptions
:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
我们来分析一下resolveConstructorOptions
:
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 有super属性,说明Ctor是Vue.extend构建的子类
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions // Vue构造函数上的options,如directives,filters,....
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
}
这个方法要分成两种情况来说明,第一种是Ctor
是基础Vue
构造器的情况,另一种是Ctor
是通过Vue.extend
方法扩展的情况。
Ctor是vue构造器时
当Ctor
是基础Vue
构造器时,如通过new
关键字来新建Vue
构造函数的实例。
const vm = new Vue({
el: '#app',
data: {
message: 'Hello Chris'
}
})
此时resolveConstructorOptions
就是直接返回options
// 这个时候options就是Vue构造函数上的options,是在initGlobalAPI(Vue)定义的
let options = Ctor.options
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
export function initGlobalAPI (Vue: GlobalAPI) {
// ...
Vue.options = Object.create(null)
// 相当于
// Vue.options.components = {}
// Vue.options.directives = {}
// Vue.options.filters = {}
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
// Vue 的内置组件目前有 <keep-alive>、<transition> 和 <transition-group> 组件
// 把一些内置组件扩展到 Vue.options.components
extend(Vue.options.components, builtInComponents)
// ...
}
所以这时候返回的options
是这样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9TDWjMQc-1588666049968)(Vue 源码解读笔记.assets/5af660430001035410420172.png)]
Ctor是通过Vue.extend创建的子类
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 有super属性,说明Ctor是Vue.extend构建的子类
if (Ctor.super) {
// 首先递归调用resolveConstructorOptions方法,返回"父类"上的options并赋值给superOptions变量
const superOptions = resolveConstructorOptions(Ctor.super)
// 然后把"自身"的options赋值给cachedSuperOptions变量
const cachedSuperOptions = Ctor.superOptions // Vue构造函数上的options,如directives,filters,....
// 然后比较这两个变量的值,当这两个变量值不等时,说明"父类"的options改变过了
if (superOptions !== cachedSuperOptions) {
// 例如执行了Vue.mixin方法,这时候就需要把"自身"的superOptions属性替换成最新的
Ctor.superOptions = superOptions
// 之后检查是否"自身"的options是否发生变化
const modifiedOptions = resolveModifiedOptions(Ctor)
// 如果”自身“有新添加的options,则把新添加的options属性添加到Ctor.extendOptions属性上
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// 调用mergeOptions方法合并"父类"构造器上的options和”自身“上的extendOptions
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
// 返回合并后的options
return options
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified // 定义modified变量
const latest = Ctor.options // 自身的options
const extended = Ctor.extendOptions // 构造"自身"时传入的options
const sealed = Ctor.sealedOptions // 执行Vue.extend时封装的"自身"options,这个属性就是方便检查"自身"的options有没有变化
// 遍历当前构造器上的options属性,如果在"自身"封装的options里没有,则证明是新添加的。执行if内的语句。调用dedupe方法,最终返回modified变量(即”自身新添加的options“)
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {
}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
}
}
return modified
}
// lateset表示的是"自身"新增的options。extended表示的是当前构造器上新增的extended options,sealed表示的是当前构造器上新增的封装options
function dedupe (latest, extended, sealed) {
// 防止生命周期构造函数重复
// 如果latest不是数组的话(lateset是"自身"新增的options),这里不需要去重,直接返回latest。如果传入的latest是数组(如果latest是数组,一般这个新增的options就是生命周期钩子函数),则遍历该数组,如果该数组的某项在extended数组中有或者在sealed数组中没有,则推送到返回数组中从而实现去重。
if (Array.isArray(latest)) {
const res = []
sealed = Array.isArray(sealed) ? sealed : [sealed]
extended = Array.isArray(extended) ? extended : [extended]
for (let i = 0; i < latest.length; i++) {
// push original options and not sealed options to exclude duplicated options
if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
return res
} else {
return latest
}
}
总结
resolveConstructorOptions
方法中判断是不是子构造器,不是的话直接返回Vue.opstions
,是的话,递归调用resolveConstructorOptions
合并组件的options
到Vue
上。
mergeOptions
mergeOptions方法的调用在
_init中,我们从之前了解到
_init是在
new Vue`时,这个一般发生在两个地方:
- 外部代码,即我们自己
new Vue
; - 子组件内部调用
new Vue
。
Vue.prototype._init = function (options?: Object) {
// 如果是子组件
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
// 否则
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
// ...
}
外部调用,直接执行mergeOptions
,我们来分析一下mergeOptions
:
// 把 parent 和 child 这两个对象根据一些合并策略,合并成一个新对象并返回
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
// 来检查 options.components 组件名称是否合法:
// 包含数字,字母,下划线,连接符,并且以字母开头
// 是否和html标签名称或svg标签名称相同
// 是否和关键字名称相同,如undefined, infinity等
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// 如果child是function类型的话,我们取其options属性作为child
if (typeof child === 'function') {
child = child.options
}
// 进行规范化操作, 分别是把options中的props,inject,directives属性转换成对象的形式
// 因为有些传入的时候可能会是数组的形式, 如 props: ['postTitle']
// 分析normalizeProps
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// 先递归把 extends 和 mixins 合并到 parent 上
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {
}
let key
// 然后遍历 parent,调用 mergeField
for (key in parent) {
mergeField(key)
}
// 然后再遍历 child
for (key in child) {
// 如果 key 不在 parent 的自身属性上,则调用 mergeField
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
// defaultStrat的逻辑是,如果child上该属性值存在时,就取child上的该属性值,如果不存在,则取parent上的该属性值
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
// normalizeProps(child, vm)
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {
}
let i, val, name
// 当props是数组的时候
// 把数组的每一项的值作为res对象的key,value值等于{type: null}
// props: ['postTitle']
// 转为 postTitle: { type: null }
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = {
type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 当props是对象时
// 遍历对象,先把对象的key值转换成驼峰的形式。
// 然后再判断对象的值,如果是纯对象(即调用object.prototype.toString方法的结果是[object Object]),则直接把对象的值赋值给res,
// 如果不是,则把{ type: 对象的值}赋给res
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: {
type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
// 如果传入的props不是纯对象也不是数组,且当前环境也不是生产环境,则抛出警告
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${
toRawType(props)}.`,
vm
)
}
options.props = res
}
在创建组件的时候,我们先来回顾一下,由于组件的构造函数是通过 Vue.extend
继承自 Vue
的,先回顾一下这个过程,代码定义在 src/core/global-api/extend.js
中。
Vue.extend = function (extendOptions: Object): Function {
Sub.options = mergeOptions(
Super.options,
extendOptions // extendOptions 对应的就是前面定义的组件对象,它会和 Vue.options 合并到 Sub.opitons 中。
)
// ...
// 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)
// ...
return Sub
}
// 子组件的初始化过程,代码定义在 src/core/vdom/create-component.js 中
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// vnode.componentOptions.Ctor 就是指向 Vue.extend 的返回值 Sub
// 接着执行 this._init(options)
// 因为 options._isComponent 为 true,那么合并 options 的过程走到了 initInternalComponent(vm, options)
return new vnode.componentOptions.Ctor(options)
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 这里的 vm.constructor 就是子组件的构造函数 Sub
const opts = vm.$options = Object.create(vm.constructor.options)
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
总结
对于 options
的合并有 2 种方式:
- 执行外部
new Vue
时,会调用mergeOptions
函数,并根据不同的选项调用不同的合并策略函数 - 子组件实例化时,会调用
initInternalComponent
函数进行合并,比外部初始化 Vue 通过mergeOptions
的过程要快,合并完的结果保留在vm.$options
中。