mergeField
mergeOptions 配置选项合并,它存在2种情况
子组件合并
和
根实例合并
, 参数中的
parent
会根据调用上下文的不同而不同,可能是
Vue.option
中的配置,也可能是
组件中用户传入的配置
mergeOptions
代码片段
var options = {}
var key
for(key in parent) {mergeField(key)}
for(key in child) {mergeField(key)}
function mergeField(key) {
var strat = strats[key] || defaultStrat
options[key] = strat(parent[key],child[key],vm)
}
strats 对象
starts 对象预制了一些方法用来处理一些固定的配置项
el 、propsData
这2个选项将使用默认合并方法defaultStrat
合并数据, 但是它们只有在通过new
创建实例的时候才有效, 如果不是在实例时合并,就会提示错误
strats.el = strats.propsData = function(parent,child,vm,key){
if(!vm) {...}
return defaultStrat(parent,child)
}
defaultStrat函数在文章最后有说明
data
合并data配置项,parentV 是 Vue.options.data
或者 一个父组件
, childV就是用户传入的 data选项, 第三个参数为vue实例
strats.data = function(parentV,childV,vm) {
if(!vm) {
if(childV && typeof childV != "function") {
return parentV
}
return mergeDataOrFn(parentV,childV)
}
return mergeDataOrFn(parentV,childV,vm)
}
第三个参数vm, 它在子组件合并时是不存在的
Sub.options = mergeOptions(
Super.options,
extendOptions
)
mergeDataOrFn
- 子组件合并
- Vue创建实例时合并
第一种: 子组件的合并
- 在通过 Vue.component 创建一个子组件的时候,合并 data 的参数是 Vue.options.data 和 子组件传入的data函数
const childVm = Vue.component("childVm ",{data(){...}})
// 模拟合并时参数
mergeDataOrFn(Vue.options.data , data)
- extend 继承另一个子组件
const super = Vue.component("super",{data(){...}})
const parent = Vue.component("parent",{data(){...}})
const child = parent.extend(super)
// 模拟合并时参数
mergeDataOrFn(super.options.data , parent.options.data)
- mixin 混入配置选项
const parent = Vue.component("parent",{})
parent.mixin({data(){...}})
// 模拟合并时参数
mergeDataOrFn(parent.options.data , data)
根据上面的原则,返回data函数
或者 mergeDataFn函数
function mergeDataOrFn(parentV,childV,vm) {
if(!vm) {
if(!childV) {
return parentV
}
if(!parentV) {
return childV
}
return function mergeDataFn() {
return mergeData(
typeof childV === "function" ? childV.call(this,this) : childV,
typeof parentV === "function" ? parentV.call(this,this) : parentV
)
}
}
}
第二种:vue实例的合并,它直接返回一个闭包函数,其实策略与子组件合并大同小异
if(!vm){
...
} else {
return function mergedInstanceDataFn() {
var instanceData = typeof childVal === 'function' ? childVal(vm,vm) : childVal
var defaultData = typeof parentVal === 'function' ? parentVal(vm,vm) : parentVal
if(instanceData) {
return mergeData(instanceData,defaultData)
} else {
return defaultData
}
}
}
props、methods、inject、computed
这几个属性公用一个函数,功能是一致的. 如果 parentV
上有这几项配置, 那么就和传入的配置项
合并到一个空的对象中并返回, 否则就直接返回用户传入的配置项 . 完整代码:
strats.props =
strats.methods =
strats.inject =
strats.computed = function (parentV,childV,vm,key) {
if(!parentV) {return childV}
var ret = Object.create(null)
extend(ret,parentV)
if(childV){extend(ret,childV)}
return ret
}
watch
先看看合并后的结果,如果两边都有数据,则合并后的watch是一个数组
watch: {
test: [fn,fn]
}
- 只要有一侧没有设置 watch,就原样返回另一侧
- 两边都有时,先遍历 parentV 的watch把属性添加到临时变量ret上,再遍历用户传入的配置
stras.watch = function (parentV,childV,vm,key) {
if(!parentV) {return childV}
var ret = {}
extend(ret,parentV)
for(var key$1 in childV) {
var parent = ret[key$1]
var child = child[key$1]
if(parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]
}
return ret
}
现在对遍历用户传入的配置做一下拆分,那么for-in的代码可以理解为:
for(var key in childV) {
var parent = parentV[key]
var child = childV[key]
if(parent && !Array.isArray(parent)) {
parent = [parent]
}
if(child && !Array.isArray(child)) {
child = [child]
}
ret[key] = parent ? parent.concat(child): child
}
provide
provide 则使用与 data 合并时一样的 mergeDataOrFn函数
, 只要有一侧没有 provide 就不会合并, 原样返回有数据的那一侧.否则返回 mergeDataFn
闭包函数,将在实例化的时候调用
strats.provide = mergeDataOrFn
components、directives、filters
这3个配置项将使用 mergeAssets
合并, 首先将 Vue.options 中对应的属性作为原型创建一个新的对象(之前Vue.extend的分析中了解到初始的Vue.options中是含有默认的 components、directives、filters),然后遍历用户传入的配置,依次添加到这个对象上
var res = Object.create(parent || null)
return extend(red,child)
/************************/
function extend(to,from) {
for(var key in from) {
to[key] = from[key]
}
return to
}
生命周期函数合并
生命周期函数使用 mergeHook
合并数据, 处理后的生命周期是一个数组
var LIFECYCLE_HOOLS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated'
]
LIFECYCLE_HOOLS.forEach(function(hook){
strats[hook] = mergeHook
})
/************************/
function mergeHook(parentV,childV) {
var res = childV ? (parentV ? parentV.concat(childV) : Array.isArray(childV) ? childV : [childV]) : [parentV]
return res ? dedupeHooks(res) : res
}
为了简述这个三元表达式,我以 if()
表述帮助分析,大致如下:
- 用户传入了某个生命周期函数并且Vue.options中也有一样的,则把用户传入的添加到数组的末尾.也就是这样形式
created: [fn,fn]
- 用户传入了但是Vue.options上没有,如果不是数组则包装成数组
[ childV ]
,否则原样返回 - 用户没有传入,但Vue.options上有,则把它包装成数组
[ parentV ]
返回
var res = null
if(childV) {
if(parentV) {
res = parentV.concat(childV)
} else {
res = Array.isArray(childV) ? childV : [childV]
}
} else {
res = [parentV]
}
最后再调用去重方法 dedupHooks
, 这部分就不分析了,代码也很简单
function dedupHooks(hooks) {
var res = []
for(var i = 0; i < hooks.length; i++) {
if(res.indexOf(hooks[i] === -1)) {
res.push(hooks[i])
}
}
return res
}
defaultStrat 合并方法
除了 strats 预制的合并方法, 其余的都用 defaultStart 来处理合并, 它内部就是一个三元表达式, 用户有传入配置就使用用户传入的,否则使用 parentV 上的
function defaultStart(parentVal,childVal) {
return childVal === undefind ? parentVal : childVal
}