一种远、為什么最終 strats.data
會(huì)被處理成一個(gè)函數(shù)?
這是因?yàn)樽6ㄟ^(guò)函數(shù)返回?cái)?shù)據(jù)對(duì)象票摇,保證了每個(gè)組件實(shí)例都有一個(gè)唯一的數(shù)據(jù)副本,避免了組件間數(shù)據(jù)互相影響砚蓬。 Vue
的初始化的時(shí)候大家會(huì)看到矢门,在初始化數(shù)據(jù)狀態(tài)的時(shí)候,就是通過(guò)執(zhí)行 strats.data
函數(shù)來(lái)獲取數(shù)據(jù)并對(duì)其進(jìn)行處理的灰蛙。
二祟剔、為什么不在合并階段就把數(shù)據(jù)合并好,而是要等到初始化的時(shí)候再合并數(shù)據(jù)摩梧?
這個(gè)問(wèn)題是什么意思呢物延?我們知道在合并階段 strats.data
將被處理成一個(gè)函數(shù),但是這個(gè)函數(shù)并沒(méi)有被執(zhí)行障本,而是到了后面初始化的階段才執(zhí)行的教届,這個(gè)時(shí)候才會(huì)調(diào)用 mergeData
對(duì)數(shù)據(jù)進(jìn)行合并處理响鹃,那這么做的目的是什么呢?
其實(shí)這么做是有原因的案训,在 Vue
的初始化的時(shí)候买置,大家就會(huì)發(fā)現(xiàn) inject
和 props
這兩個(gè)選項(xiàng)的初始化是先于 data
選項(xiàng)的,這就保證了我們能夠使用 props
初始化 data
中的數(shù)據(jù)强霎,如下:
// 子組件:使用 props 初始化子組件的 childData
const Child = {
template: '<span></span>',
data () {
return {
childData: this.parentData
}
},
props: ['parentData'],
created () {
// 這里將輸出 parent
console.log(this.childData)
}
}
var vm = new Vue({
el: '#app',
// 通過(guò) props 向子組件傳遞數(shù)據(jù)
template: '<child parent-data="parent" />',
components: {
Child
}
})
如上例所示忿项,子組件的數(shù)據(jù) childData
的初始值就是 parentData
這個(gè) props
。而之所以能夠這樣做的原因有兩個(gè)
- 1城舞、由于
props
的初始化先于data
選項(xiàng)的初始化 - 2轩触、
data
選項(xiàng)是在初始化的時(shí)候才求值的,你也可以理解為在初始化的時(shí)候才使用mergeData
進(jìn)行數(shù)據(jù)合并家夺。
三脱柱、你可以這么做。
在上面的例子中拉馋,子組件的 data 選項(xiàng)我們是這么寫的:
data () {
return {
childData: this.parentData
}
}
但你知道嗎榨为,你也可以這么寫:
data (vm) {
return {
childData: vm.parentData
}
}
// 或者使用更簡(jiǎn)單的解構(gòu)賦值
data ({ parentData }) {
return {
childData: parentData
}
}
我們可以通過(guò)解構(gòu)賦值的方式,也就是說(shuō) data 函數(shù)的參數(shù)就是當(dāng)前實(shí)例對(duì)象煌茴。那么這個(gè)參數(shù)是在哪里傳遞進(jìn)來(lái)的呢随闺?其實(shí)有兩個(gè)地方,其中一個(gè)地方我們前面見過(guò)了蔓腐,如下面這段代碼:
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
注意這里的 childVal.call(this, this) 和 parentVal.call(this, this)矩乐,關(guān)鍵在于 call(this, this),可以看到回论,第一個(gè) this 指定了 data 函數(shù)的作用域散罕,而第二個(gè) this 就是傳遞給 data 函數(shù)的參數(shù)。
當(dāng)然了僅僅在這里這么做是不夠的透葛,比如 mergedDataFn 前面的代碼:
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
在這段代碼中笨使,直接將 parentVal 或 childVal 返回了,我們知道這里的 parentVal 和 childVal 就是 data 函數(shù)僚害,由于被直接返回,所以并沒(méi)有指定其運(yùn)行的作用域繁调,且也沒(méi)有傳遞當(dāng)前實(shí)例作為參數(shù)萨蚕,所以我們必然還是在其他地方做這些事情,而這個(gè)地方就是我們說(shuō)的第二個(gè)地方蹄胰,它在哪里呢岳遥?當(dāng)然是初始化的時(shí)候,后面我們會(huì)講到的裕寨,如果這里大家沒(méi)有理解也不用擔(dān)心浩蓉。
四派继、mergeHook時(shí)的三目運(yùn)算符
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
解析
return (是否有 childVal,即判斷組件的選項(xiàng)中是否有對(duì)應(yīng)名字的生命周期鉤子函數(shù))
? 如果有 childVal 則判斷是否有 parentVal
? 如果有 parentVal 則使用 concat 方法將二者合并為一個(gè)數(shù)組
: 如果沒(méi)有 parentVal 則判斷 childVal 是不是一個(gè)數(shù)組
? 如果 childVal 是一個(gè)數(shù)組則直接返回
: 否則將其作為數(shù)組的元素捻艳,然后返回?cái)?shù)組
: 如果沒(méi)有 childVal 則直接返回 parentVal
根據(jù) mergeHooks返回值驾窟,其返回值是一個(gè)數(shù)組,因此我們?cè)趯懮芷跁r(shí)认轨,可以直接傳遞數(shù)組绅络,并且其將按照順序執(zhí)行。
new Vue({
created: [
function () {
console.log('first')
},
function () {
console.log('second')
},
function () {
console.log('third')
}
]
})
五嘁字、資源(assets)選項(xiàng)的合并策略
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
舉個(gè)例子恩急,大家知道任何組件的模板中我們都可以直接使用 <transition/> 組件或者 <keep-alive/> 等,但是我們并沒(méi)有在我們自己的組件實(shí)例的 components 選項(xiàng)中顯式地聲明這些組件纪蜒。那么這是怎么做到的呢衷恭?其實(shí)答案就在 mergeAssets 函數(shù)中。以下面的代碼為例:
var v = new Vue({
el: '#app',
components: {
ChildComponent: ChildComponent
}
})
上面的代碼中纯续,我們創(chuàng)建了一個(gè) Vue 實(shí)例匾荆,并注冊(cè)了一個(gè)子組件 ChildComponent,此時(shí) mergeAssets 方法內(nèi)的 childVal 就是例子中的 components 選項(xiàng):
components: {
ChildComponent: ChildComponent
}
而 parentVal 就是 Vue.options.components杆烁,我們知道 Vue.options 如下:
Vue.options = {
components: {
KeepAlive,
Transition,
TransitionGroup
},
directives: Object.create(null),
directives:{
model,
show
},
filters: Object.create(null),
_base: Vue
}
所以 Vue.options.components 就應(yīng)該是一個(gè)對(duì)象:
{
KeepAlive,
Transition,
TransitionGroup
}
也就是說(shuō) parentVal 就是如上包含三個(gè)內(nèi)置組件的對(duì)象牙丽,所以經(jīng)過(guò)如下這句話之后:
const res = Object.create(parentVal || null)
你可以通過(guò) res.KeepAlive 訪問(wèn)到 KeepAlive 對(duì)象,因?yàn)殡m然 res 對(duì)象自身屬性沒(méi)有 KeepAlive兔魂,但是它的原型上有烤芦。
然后再經(jīng)過(guò) return extend(res, childVal) 這句話之后,res 變量將被添加 ChildComponent 屬性析校,最終 res 如下:
res = {
ChildComponent
// 原型
__proto__: {
KeepAlive,
Transition,
TransitionGroup
}
}
所以這就是為什么我們不用顯式地注冊(cè)組件就能夠使用一些內(nèi)置組件的原因构罗,同時(shí)這也是內(nèi)置組件的實(shí)現(xiàn)方式,通過(guò) Vue.extend 創(chuàng)建出來(lái)的子類也是一樣的道理智玻,一層一層地通過(guò)原型進(jìn)行組件的搜索遂唧。
六、選項(xiàng)處理小結(jié)
現(xiàn)在我們了解了 Vue 中是如何合并處理選項(xiàng)的吊奢,接下來(lái)我們稍微做一個(gè)總結(jié):
- 對(duì)于 el盖彭、propsData 選項(xiàng)使用默認(rèn)的合并策略 defaultStrat。
- 對(duì)于 data 選項(xiàng)页滚,使用 mergeDataOrFn 函數(shù)進(jìn)行處理召边,最終結(jié)果是 data 選項(xiàng)將變成一個(gè)函數(shù),且該函數(shù)的執(zhí)行結(jié)果為真正的數(shù)據(jù)對(duì)象裹驰。
- 對(duì)于 生命周期鉤子 選項(xiàng)隧熙,將合并成數(shù)組,使得父子選項(xiàng)中的鉤子函數(shù)都能夠被執(zhí)行
- 對(duì)于 directives幻林、filters 以及 components 等資源選項(xiàng)贞盯,父子選項(xiàng)將以原型鏈的形式被處理音念,正是因?yàn)檫@樣我們才能夠在任何地方都使用內(nèi)置組件、指令等躏敢。
- 對(duì)于 watch 選項(xiàng)的合并處理闷愤,類似于生命周期鉤子,如果父子選項(xiàng)都有相同的觀測(cè)字段父丰,將被合并為數(shù)組肝谭,這樣觀察者都將被執(zhí)行。
- 對(duì)于 props蛾扇、methods攘烛、inject、computed 選項(xiàng)镀首,父選項(xiàng)始終可用坟漱,但是子選項(xiàng)會(huì)覆蓋同名的父選項(xiàng)字段。
- 對(duì)于 provide 選項(xiàng)更哄,其合并策略使用與 data 選項(xiàng)相同的 mergeDataOrFn 函數(shù)芋齿。
最后,以上沒(méi)有提及到的選項(xiàng)都將使默認(rèn)選項(xiàng) defaultStrat成翩。
最最后觅捆,默認(rèn)合并策略函數(shù) defaultStrat 的策略是:只要子選項(xiàng)不是 undefined 就使用子選項(xiàng),否則使用父選項(xiàng)麻敌。