Vue的mergeOptions函数的主要做用是用于合并选项(将俩个选项对象合并成一个),它是用于实例化和继承的核心函数。这也是为何咱们要去分析它。而且与函数相关的选项合并策略也都在一个文件里,定义在/src/core/util/options.js
文件中。javascript
由于Vue的核心代码都是放在src文件夹下,因此咱们能够在src目录下全局搜索下mergeOptions
的使用场景,能够发现函数在Vue.extend
、Vue.mixin
、实例化
都有用到。(只考虑web平台)html
// src/core/global-api/extend.js文件中
Vue.extend = function (extendOptions: Object): Function {
// ... 忽略无关代码
Sub.options = mergeOptions(
Super.options,
extendOptions
)
}
// src/core/global-api/mixin.js文件中
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
// src/core/instance/init.js文件中 执行new 实例化的时候会执行
Vue.prototype._init = function (options?: Object) {
// ... 忽略无关代码
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
复制代码
这也证明了mergeOptions函数的注释所写的同样,Core utility used in both instantiation and inheritance.
。vue
mergeOptions函数被定义在/src/core/util/options.js
文件中,源代码以下:html5
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */
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
}
normalizeProps(child, vm)
normalizeInject(child, vm)
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.
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)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
复制代码
咱们先看函数接受的参数,这里有一点过要注意,mergeOptions函数第三个参数的可选的,能够不传。Vue.mixin
、Vue.extend
函数中调用mergeOptions的时候是不传第三个参数的。选项的合并策略函数会根据vm参数来肯定是实例化选项合并仍是继承选项合并,从而作不一样的处理,这个后面会详细讲到。java
函数第一行,检查非生产环境下,执行checkComponents
函数,该函数定义在同一文件下,主要是检查组件的名字是否符合规范。能够看到核心函数是validateComponentName
,并且它被暴露出去,由于在Vue.component()
、Vue.extend()
函数中都有用到。web
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
/** * 检验组件的名字 */
function checkComponents (options: Object) {
// 遍历对象的components属性,依次检验
for (const key in options.components) {
validateComponentName(key)
}
}
// 若是检验不经过,给出相应警告
export function validateComponentName (name: string) {
// 符合HTML5规范,由普通字符和中横线(-)组成,而且必须以字母开头。
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
)
}
// isBuiltInTag是检验名字不能与slot,component重名
// isReservedTag是检验不能与html、svg内置标签重名
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}
复制代码
接下来是检查传入的child是不是函数,若是是的话,取到它的options选项从新赋值给child。因此说child参数能够是普通选项对象,也能够是Vue构造函数和经过Vue.extend
继承的子类构造函数。(Vue.options定义在src/core/global-api/index.js
文件中)api
if (typeof child === 'function') {
child = child.options
}
复制代码
再日后看有三个函数,分别是normalizeProps
、normalizeInject
、normalizeDirectives
,它们的做用是规范化选项,用过Vue的同窗应该都知道,咱们在写props
、inject
既能够是字符串数组,也能够是对象。directives
既能够是一个函数,也能够是对象。Vue对外提供了便捷的写法,但内部处理要把他们规范成同样,才更方便处理。其实三个函数都是将选项转换对象的形式,接下来咱们会逐个分析。数组
function normalizeProps (options: Object, vm: ?Component) {
// 定义props,是选项中的props属性的引用
const props = options.props
if (!props) return
const res = {}
let i, val, name
// 1. 是数组的状况 例如:['name', 'age']
if (Array.isArray(props)) {
i = props.length
// 循环遍历变成对象格式{ type: null }
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val) // 将key值变成驼峰形式
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
// 若是不是字符串数组,非生产环境给出警告
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
// 2. 是对象
for (const key in props) {
val = props[key]
name = camelize(key)
// 若是是对象,则直接赋值,不是的话,则赋值type属性
// 例如 { sex: String, job: { type: String, default: 'xxx' } }
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
// 不是数组和对象给出警告
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
// 规范后结果赋值给options.props
options.props = res
}
复制代码
['name', 'age']
),说明只指定了key值,只须要将数组遍历,转成对象形式,把type属性设置null,当传入的是对象时,又分为俩种状况,一种是key值对应的是对象,那直接赋值就好。不然那表明只指定了类型(例如:
{ sex: String, }
),一样转成对象形式。
function normalizeInject (options: Object, vm: ?Component) {
// 取到options.inject的引用
const inject = options.inject
if (!inject) return
// 重置对象,以后从新赋值属性
const normalized = options.inject = {}
// 1. 数组状况,直接遍历。与normalizeProps同理
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
// 2. 对象状况。若是key值对应的是对象,则经过exntend合并,若是不是,则表明直接是from
for (const key in inject) {
const val = inject[key]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
复制代码
extend
合并对象。由于from属性不能为空,因此若是对象中没有from属性,默认仍是赋予同名的from。不然就会被覆盖。例如:如上图中的age属性的from值
parentAge
就会覆盖默认的age,而job属性没有指定from,因此会赋予同名的from属性。
function normalizeDirectives (options: Object) {
const dirs = options.directives
// 遍历对象,若是key值对应的是函数。则修改为对象形式。
// Vue提供了自定义指令的简写,若是只传函数,等同于{ bind: func, update: func }
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
复制代码
以上三个函数每一个if分支都是根据Vue提供的feature来进行不一样的处理,其根本目的就是为了使传入的参数统一。若是你对哪一个分支还有疑惑,能够去阅读下相关的官方文档。props、inject、directives。ide
咱们回到mergeOptions
函数继续往下看,这里判断没有_base属性的话(被合并过再也不处理,只有合并过的选项会带有_base属性),处理子选项的extend、mixins,处理方法就是将extend和mixins再经过mergeOptions
函数与parent合并,由于mergeOptions函数合并后会返回新的对象,因此这时parent已是个崭新的对象啦。svg
if (!child._base) {
// 若是有extends属性(`extends: xxx`),则仍是调用mergeOptions函数返回的结果赋值给parent
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
// 若是有mixins属性(`mixins: [xxx, xxx]`)
// 则遍历数组,递归调用mergeOptions,结果也赋值给parent
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
复制代码
接下来的最后一段代码以下:
// 定义options为空对象,最后函数返回结果是options
const options = {}
let key
// 先遍历parent执行mergeField
for (key in parent) {
mergeField(key)
}
// 再遍历child,当parent没有key的时候,在执行mergeField。
// 若是有key属性,就不须要合并啦,由于上一步已经合并到options上了
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 该函数主要是经过key获取到对应的合并策略函数,而后执行合并,赋值给options[key]
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
复制代码
到最后能够知道,mergeOptions
函数进行真正的合并是最后一段代码,前面都是对选项进行规范化,以及extend
、mixins
进行递归合并。那strat是啥呢?其实它是文件顶部定义的一个对象,它是config.optionMergeStrategies
的引用,而且在以后对特殊的合并策略进行了重写,好比说el
、 data
、钩子函数
、components
、props
、methods
等等。合并策略相关的代码咱们在下一篇进行分析。