Vue2.0 源碼分析筆記(四)選項(xiàng)的合并

一种远、為什么最終 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) injectprops 這兩個(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)行組件的搜索遂唧。


image.png

六、選項(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)麻敌。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栅炒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子术羔,更是在濱河造成了極大的恐慌赢赊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件级历,死亡現(xiàn)場(chǎng)離奇詭異释移,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)寥殖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門玩讳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扛禽,你說(shuō)我怎么就攤上這事锋边。” “怎么了编曼?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)剩辟。 經(jīng)常有香客問(wèn)我掐场,道長(zhǎng)往扔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任熊户,我火速辦了婚禮萍膛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚷堡。我一直安慰自己蝗罗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布蝌戒。 她就那樣靜靜地躺著串塑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪北苟。 梳的紋絲不亂的頭發(fā)上桩匪,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音友鼻,去河邊找鬼傻昙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛彩扔,可吹牛的內(nèi)容都是我干的妆档。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼虫碉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贾惦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蔗衡,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纤虽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后绞惦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑟蜈,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年菇用,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肺然。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡王滤,死狀恐怖贺嫂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雁乡,我是刑警寧澤第喳,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站踱稍,受9級(jí)特大地震影響曲饱,放射性物質(zhì)發(fā)生泄漏悠抹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一扩淀、第九天 我趴在偏房一處隱蔽的房頂上張望楔敌。 院中可真熱鬧,春花似錦驻谆、人聲如沸卵凑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勺卢。三九已至,卻和暖如春区端,著一層夾襖步出監(jiān)牢的瞬間值漫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工织盼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杨何,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓沥邻,卻偏偏與公主長(zhǎng)得像危虱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唐全,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容