紧接上文mergeOptions
回顾一下之前在哪里用到的mergeOptions
Vue.prototype._init = function (options) {
...
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
...
}
先来看一下
resolveConstructorOptions吧
function resolveConstructorOptions (Ctor) {
let options = Ctor.options
if (Ctor.super) {
...
}
return options
}
获取
Ctor的options, 根据第一个代码片段我们可以得出Ctor目前是Vue构造函数。很显然Vue就已经是根实例了,我们现在不考虑有组件的情况,有组件存在,组件上就拥有super了, 因为他继承了Vue的根实例。不存在super,所以就直接返回Ctor.options,也就是Vue构造函数的options。
接下来是真正的重头戏,
mergeOptions, 我们找到mergeOptions源码所在。src/utils/options
mergeOptions
/*
* Merge two option object into a new one. 合并两个option对象到一个新的对象中去
* Core utility used in both instantiation and inheritance. 用于实例化和继承的核心程序
*/
// 对应上面传入的内容哈
function mergeOptions (
parent, // Vue.options => {components: {}, directives: {}, filters: {}}
child, // options || {}
vm // vm vue实例
) {
normalizeProps(child, vm) // 格式化props
normalizeInject(child, vm) // 格式化inject
normalizeDirectives(child, vm) // 格式化指令 这些都不是我们研究的重点
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)
}
}
} // 判断有没有extends mixins 如果有就递归调用mergeOptions 将他们合并到parent上。
const options = {} // 创建一个新对象,用来存储parent和child的合并结果
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = starts[key] || defaultStarat
options[key] = start(parent[key], child[key], vm, key)
}
return options
}
讲道理合并过程还是真挺繁琐的,因为涉及到
data、computed、methods、watch、components、filters、mixins、extends、directives、生命周期等的合并,这里我们挑几个做讲解就好,其他的可以到src/utils/options中查看,不难。 拿合并data、生命周期两个例子来说吧。
mergeOptions之合并data
// data的合并
strats.data = function (
parentVal,
childVal,
vm
) {
if (!vm) {
if (childVal && typeof childVal !== "function") {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option shoule be a function' +
'that returns a per-instance value in component' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
呦, 看到之前写vue的时候出的错了, 知道为什么
data必须是一个函数的报错提醒原来在这里。哇哦!surprise。注意这里data必须是一个函数,指的是组件中的data,Vue根实例既可以是一个简单的对象也可以是一个函数。 废话不多说,直接看mergeDataOrFn这个函数
function mergeDataOrFn(
parentVal,
childVal,
vm
) {
// ... 这里先不考虑有组件情况。
return function mergeInstanceDataFn () {
const instanceData = typeof childVal = "function"
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal = "function"
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
原来如此, 我们最后合并data的操作, 最终返回了一个函数
mergeInstanceDataFn, 这个函数用来将两者合并。问题来了, 这个函数在哪里用到呢? 后面介绍。data的合并过程就完事了。
mergeOptions之 合并生命周期
接着我们看一下合并生命周期
LIFYCYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
LIFY_HOOKS的定义在 shared/constants.js
看一下mergeHook,理解为合并钩子,中式翻译,哈哈
function mergeHook (
parentVal,
childVal
) {
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
return res ? dedupeHooks(res) : res
}
判断
childVal和parentVal都存在 就让parentVal.concat(childVal), 如果parentVal不存在,那就看childVal是否是数组,如果是直接返回,如果不是就把他变成数组,如果childVal不存在那就直接返回parentVal,然后赋值给res,最后看res是否存在,若不存在就返回res, 若存在就调用dedupeHooks,这里我们知道生命周期的钩子都是以数组的形式保存的。然后看一下dedupeHooks,去重钩子
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
这段代码就不解释了吧, 简单的数组去重操作。就介绍到这吧。把这写合并好了, 其实都是合并到之前我们在
mergeOptions中的定义的那个新的options空对象,最后再将这个options对象返回回去。就完成了合并的工作。
function mergeOptions (
parentVal,
childVal,
vm
) {
...
const options = {}
...
return options
}
github地址: https://github.com/ComponentTY/vue-sourcecode 写得不好, 还望各位大佬指教。