隨着對Vue源碼的深入,越來越覺得它的結構還是清晰的。想想也是這樣的,如果不清晰,它們的開發人員也很難維護的呀。
衆所周知在創建Vue實例是需要傳遞一個對象的,我們稱之爲選項(options)。最終每個實例會有一個options並不是創建Vue實例是通過構造函數傳來的那個,它是綜合了好幾個層次的options產生出來的。這個綜合的過程就是合併。
首先說一下,它綜合了哪幾個層次?先貼源碼
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
單從這段代碼上我們能知道它綜合了組件構造函數和創建實例時傳來的options。
- 組件類的options
- 創建實例時傳來的options
如果再深入的話,組件類的options其實並不是簡單的定義組件類時傳入的options,而是綜合了定義組件類傳入的options和該組件類的父組件(一般就是Vue類)的options。
像我們平時全局註冊一個組件,我們用來註冊組件的那個其實只是組件的options(只是一個Object),只有調用了Vue.component(這個方法可以在core/global/assets.js中查看)纔會把這個組件options轉換成一個組件類(可以查看core/global-api/extend.js),創建的這個組件類會保存在Vue.components中,當然了Vue.component這方法也會返回這個組件類。
接下來說說合並的策略吧。options中有很多字段,各個字段的合併策略是不一樣的。想讓我們搞清楚options這個對象到底能接收哪些一級字段吧。我並沒有找到源碼裏有明確定義了一個options對象的全部字段的地方,我只能從mergeOptions這個函數中總結了:
- data
- watch
- methods
- props
- inject
- computed
- provide
- 生命週期函數beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed activated deactivated
- 資源對象components filters directive
- 別的
提一句,這些合併策略放到了Vue.config.optionMergeStrategies中。
讓我們一個一個的看下它們的合併策略吧
data和provide
data和provide要求是函數。兩個不同options中的合併策略是:合併兩個函數調用後返回的對象。下面是合併對象的策略。
/**
* Helper that recursively merges two data objects together.
*/
function mergeData (to: Object, from: ?Object): Object {
// 沒有源對象,直接把目標對象返回
if (!from) return to
let key, toVal, fromVal
// 收集源對象的key
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
// 遍歷源對象每個字段,根據具體情況合併到目標對象
for (let i = 0; i < keys.length; i++) {
key = keys[i]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
// 目標對象中沒有這個字段,把這個字段放到目標中
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if ( // 目標對象中有這個字段,說明這個字段在源對象和目標對象中都有。同時這兩個都是對象的話,要遞歸合併了
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal)
}
}
return to
}
總結一下合併的data的過程
以源對象爲基礎,遍歷源對象所有的key
- 如果目標對象沒有這個key的話,就把這個key對應的值放到目標對象
- 如果目標對象有這個key,又有兩種情況
- 源對象和目標對象中這個key對應的值相同,啥也不做
- 源對象和目標對象中這個key對應的值不相同且都是對象,則把它們倆合併
methods、props、inject、computed
先回憶一下methods、props、inject和computed在options中出現的形式,
- methods一定是對象形式的,而且沒有嵌套
- computed一定是對象形式的,而且沒有嵌套
- props和inject可以是字符串數組也可以是對象形式,字符串形式會被最終標準化對象形式的,因此最後到合併策略的時候也只是對象形式,而且沒有嵌套
因此這四個的合併策略是一樣的,是針對沒嵌套的對象的合併,即綜合兩個對象中的字段,子組件options中的優先級高。
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
directives、filters和components
這三個在vue中都屬於資源類,在shared/constants.js中定義了一個數組ASSET_TYPES,只有這三個。
說實話這個策略的結果跟上個策略的結果我覺得沒有什麼區別,最後都是子組件的options中的優先級高。
區別在於,上個策略的結果,每個字段都是屬於返回的對象的,而這個策略的結果對象中的父組件的options是在返回對象的原型鏈上的。
生命週期函數的合併策略
生命週期函數有哪些呢?參考shared/constants.js中的LIFECYCLE_HOOKS
就是數組的合併,不存在覆蓋的情況,各個options中的相同類型的hook函數綜合到一個數組中,父組件的在前面子組件的在後面。
watch
我看了好幾遍代碼,watch的合併跟生命週期函數的合併效果是一樣的呀
別的字段的策略
如果不在以上這些字段內的,都屬於別的,就是優先取子組件的options內容,如果子options中沒有就取父options中的內容。
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
說一下mergeOptions的過程
傳進來兩個options讓mergeOptions合併。進來第一件事就是把props、inject和directive進行標準化,都轉換成對象形式的。
然後再處理子options上的extends和mixins。
最後對每一個key調用對應的合併策略進行合併!
PS: 如果有幫助,請點贊哦:)