# 【源码】mixin 源码解析
# 前言
今天开始探索mixin的源码,我们可以知道mixin是什么时候进行合并的以及对各个类型是如何进行合并的。
# 从源码看 Vue 中的 Mixin
我们先从Vue.mixin
入手看源码
// vue/src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
我们在使用Vue.mixin
的时候传入了一个对象,也就是源码中的形参mixin
,然后调用mergeOptions
方法将全局基础options和传入的mixin
进行合并
那么全局的基础options有什么呢?
// vue/src/core/global-api/index.js
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
其中ASSET_TYPES
是['component', 'directive', 'filter']
,也就是全局的基础options是'component', 'directive', 'filter'
我们继续来看下mergeOptions
方法是如何进行合并的(代码有删减)
// vue/src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (child.mixins) { // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
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) // 先遍历parent的key 调对应的strats[XXX]方法进行合并
}
for (key in child) {
if (!hasOwn(parent, key)) { // 如果parent已经处理过某个key 就不处理了
mergeField(key) // 处理child中的key 也就parent中没有处理过的key
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
}
return options
}
上述代码的作用主要是这3点
优先递归处理 mixins
先遍历合并 parent 中的key,调用
mergeField
方法进行合并,然后保存在变量options再遍历 child,合并补上 parent 中没有的key,调用
mergeField
方法进行合并,保存在变量options
其实核心在于strats
中对应的不同类型的处理方法,我们接下来分为几种类型来看下对应的合并策略
# 替换型
props
、methods
、inject
、computed
属于替换式,我们来看下代码
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
const ret = Object.create(null) // 创建一个第三方对象 ret
extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
return ret
}
strats.provide = mergeDataOrFn
从上述代码可以看到props
、methods
、inject
、computed
的合并策略都是将新的同名参数替代旧的参数,也就是所说的替换型
# 合并型
data
属于合并型,我们将源码简写一下
strats.data = function(parentVal, childVal, vm) {
return mergeDataOrFn(
parentVal, childVal, vm
)
};
function mergeDataOrFn(parentVal, childVal, vm) {
return function mergedInstanceDataFn() {
var childData = childVal.call(vm, vm) // 执行data挂的函数得到对象
var parentData = parentVal.call(vm, vm)
if (childData) {
return mergeData(childData, parentData) // 将2个对象进行合并
} else {
return parentData // 如果没有childData 直接返回parentData
}
}
}
function mergeData(to, from) {
if (!from) return to
var key, toVal, fromVal;
var keys = Object.keys(from);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
toVal = to[key];
fromVal = from[key];
// 如果不存在这个属性,就重新设置
if (!to.hasOwnProperty(key)) {
set(to, key, fromVal);
}
// 存在相同属性,合并对象
else if (typeof toVal =="object" && typeof fromVal =="object") {
mergeData(toVal, fromVal);
}
}
return to
}
从代码可以看到这里遍历了要合并的 data 的所有属性,然后根据不同情况进行合并:
- 当目标 data 对象不包含当前属性时,调用 set 方法进行合并,后面讲 set。
- 当目标 data 对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性。
set方法其实就是一些合并重新赋值的方法,这里就不展开来说了
# 队列型
全部生命周期函数和watch
都是队列型合并策略
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
代码比较简单,Vue 实例的生命周期钩子被合并为一个数组,然后正序遍历一次执行。watch
的策略差不多,这里就不展开了
# 叠加型
component
、directives
、filters
属于叠加型的策略,但是业务中用的是真的少
strats.components=
strats.directives=
strats.filters = function mergeAssets(
parentVal, childVal, vm, key
) {
var res = Object.create(parentVal || null);
if (childVal) {
for (var key in childVal) {
res[key] = childVal[key];
}
}
return res
}
通过原型链进行层层的叠加
# 总结
代码有点多,如果还是不理解的话没有关系,我们来大致总结一下就好了
在我们调用Vue.mixin的时候会通过
mergeOptions
方法将全局基础options(component', 'directive', 'filter)进行合并在
mergeOptions
内部优先进行mixins的递归合并,然后先父再子调用mergeField
进行合并,不同的类型走不同的合并策略替换型策略有
props
、methods
、inject
、computed
, 就是将新的同名参数替代旧的参数合并型策略是
data
, 通过set
方法进行合并和重新赋值队列型策略有生命周期函数和
watch
,原理是将函数存入一个数组,然后正序遍历依次执行叠加型有
component
、directives
、filters
,将回调通过原理链联系在一起