
簡(jiǎn)介
在Vue的初始化過(guò)程中,最開(kāi)始的階段就是選項(xiàng)合并階段。它通過(guò)調(diào)用mergeOptions函數(shù)將兩個(gè)選項(xiàng)配置合并成一個(gè)選項(xiàng)配置。這里的選項(xiàng)options的形式實(shí)際上就是我們平時(shí)開(kāi)發(fā)時(shí)在Vue中寫(xiě)的對(duì)象配置,形式如下:
{
components: {},
filters: {},
data() { return {} },
computed: {},
created: {},
methods: {},
...
}
因此,選項(xiàng)合并實(shí)際可以簡(jiǎn)單的看作是兩個(gè)上面的對(duì)象合并成一個(gè)對(duì)象。
由于mergeOptions是實(shí)現(xiàn)實(shí)例化(new Vue(options))、繼承(Vue.extend)和混入(Vue.mixin)三大功能的核心函數(shù),所以分析它的實(shí)現(xiàn)是理解Vue實(shí)例化過(guò)程和繼承的必經(jīng)之路。 下面我們將從以下幾個(gè)方面來(lái)全面了解Vue中的選項(xiàng)合并:
實(shí)例化過(guò)程中的選項(xiàng),了解我們需要合并的選項(xiàng)是怎樣的。
mergeOptions的實(shí)現(xiàn),了解各個(gè)合并策略。繼承(
Vue.extend和extends:{})的選項(xiàng)合并。混入(
Vue.mixin和mixins:[])的選項(xiàng)合并。為什么實(shí)例化過(guò)程中有時(shí)用
initInternalComponent而不是mergeOptions。
實(shí)例化過(guò)程中的選項(xiàng)
從上一節(jié)的學(xué)習(xí)我們知道,Vue的實(shí)例化過(guò)程調(diào)用的是core/instance/init.js文件中的_init方法。
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
...
}
實(shí)例化的過(guò)程中第一個(gè)重要的處理就是選項(xiàng)的合并,這里第二個(gè)參數(shù)比較容易理解,就是我們平時(shí)寫(xiě)的Vue的配置項(xiàng)。第一個(gè)參數(shù)則是通過(guò)resolveConstructorOptions(vm.constructor)生成,找到resolveConstructorOptions方法,代碼如下
// * 返回構(gòu)造函數(shù)的 options
export function resolveConstructorOptions (Ctor: Class<Component>) {
// * 如果不是繼承,options 就是原構(gòu)造函數(shù)的 options
// * 如果是繼承時(shí),options 為合并 superOptions 和 extendOptions 的 options
// * 此外,這里的 options 還包含了全局注冊(cè)的 組件/指令/過(guò)濾器
let options = Ctor.options
// * Ctor.super 存在說(shuō)明是調(diào)用了 extend 方法進(jìn)行繼承生成的構(gòu)造函數(shù)
// * 詳見(jiàn) /src/core/global-api/extend.js 文件
// * - superOptions 是父類 options
// * - extendOptions 是當(dāng)前類傳入的 options (如果與 sealedOptions不同,需要合并)
// * - options = mergeOptions(superOptions, extendOptions)
// * - sealedOptions 保存的是當(dāng)前類繼承時(shí) 合并后的 options(是extend的時(shí)候賦值的)
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
// * 如果 superOptions 變動(dòng)了,需要處理新的 options
if (superOptions !== cachedSuperOptions) {
Ctor.superOptions = superOptions
// * sealedOptions 是 seal 的時(shí)候賦值的,
// * 這里的變動(dòng)可能是 options 在 extend 后繼續(xù)被賦值
// * 復(fù)現(xiàn):https://jsfiddle.net/vvxLyLvq/2/
// * 所以需要找出變動(dòng)了的屬性,然后更新到 extendOptions 上
// * 這里的 extend 只是對(duì)象的合并
const modifiedOptions = resolveModifiedOptions(Ctor)
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
// * 由于 options 變化了,重新合并一次
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
// * 將自身的構(gòu)造函數(shù)也存到了 components 對(duì)象中
options.components[options.name] = Ctor
}
}
}
return options
}
這里傳入的是當(dāng)前構(gòu)造函數(shù),那么Ctor.options指的是Vue構(gòu)造函數(shù)的options,還記得上一節(jié)中提到的Vue.options嗎?這里將options的內(nèi)容從上一節(jié)中復(fù)制過(guò)來(lái),代碼如下:
// Vue.options 內(nèi)容
{
components: {
KeepAlive,
// 新增 platformComponents
Transition,
// 新增 platformComponents
TransitionGroup
},
filters: {},
directives: {
// 新增 platformDirectives
model,
// 新增 platformDirectives
show
},
_base: Vue
}
再看下一句Ctor.super的判斷,super這個(gè)字段是在core/global-api/extend.js文件中的extend方法調(diào)用時(shí)添加的。如果Ctor.super存在,說(shuō)明Ctor是通過(guò)繼承而來(lái)的子構(gòu)造函數(shù)。但是,如果在extend后,我們又在父構(gòu)造函數(shù)的options上添加新的屬性,這個(gè)時(shí)候子構(gòu)造函數(shù)是無(wú)法繼承新的屬性的。因此,這里需要通過(guò)Ctor.super向上尋找,找出所有父構(gòu)造函數(shù)更新的options屬性,并更新到子構(gòu)造函數(shù)上,這樣就能解決Vue.options被更改的問(wèn)題了。有興趣的話,可以看一下Vue的issues#4976。
最后,經(jīng)過(guò)resolveConstructorOptions處理后,最終得到的同樣是一個(gè)Vue的配置選項(xiàng),下一步則是需要將這兩個(gè)配置選項(xiàng)進(jìn)行合并了。
mergeOptions的實(shí)現(xiàn)
選項(xiàng)校驗(yàn)和規(guī)范化
mergeOptions函數(shù)的定義是在/src/core/util/options.js文件當(dāng)中,部分代碼如下:
export function mergeOptions (
parent: Object,// 選項(xiàng)
child: Object, // 選項(xiàng)
vm?: Component // Vue 實(shí)例
): Object {
// * 1. 校驗(yàn)選項(xiàng)中的 components 里的名稱是否合法。
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// * ['type1'], { type2: { type: String, default: '' } } 兩種形式
// * 2. 都轉(zhuǎn)換成后一種形式
normalizeProps(child, vm)
// * ['injectKey1'], { injectKey2: { from: 'xxx', default: 'yyy' } } 兩種形式
// * 3. 都轉(zhuǎn)換成后一種形式
normalizeInject(child, vm)
// * directive 有兩種形式 function() {} 或者是 { bind, update, ... }
// * 4. 都轉(zhuǎn)換成后一種形式
normalizeDirectives(child)
// * 合并策略
...
}
在正式合并之前,會(huì)優(yōu)先校驗(yàn)components里的組件名稱是否合法,如果不合法會(huì)進(jìn)行提示。
function checkComponents (options: Object) {
for (const key in options.components) {
validateComponentName(key)
}
}
// 校驗(yàn)組件名稱是否合法
export function validateComponentName (name: string) {
// 1. 判斷組件名是否合法,如數(shù)字開(kāi)頭的則不合法
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
)
}
// 2. 判斷組件是否是自身定義的組件名,如 slot 等。
// 3. 判斷組件是否是 html 中的標(biāo)簽名,如 div 等
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}
除此之外, Vue還做了以下幾點(diǎn)處理。通過(guò)選項(xiàng)形式的轉(zhuǎn)換,將多種寫(xiě)法的選項(xiàng)轉(zhuǎn)換成統(tǒng)一形式:
normalizeProps方法:處理props,將數(shù)組形式定義的props轉(zhuǎn)換成對(duì)象形式。normalizeInject方法:處理inject,將數(shù)組形式定義的inject轉(zhuǎn)換成對(duì)象形式。normalizeDirectives方法:處理directives,將指令數(shù)組里函數(shù)形式定義的directive轉(zhuǎn)換成對(duì)象形式。
處理前和處理后對(duì)比結(jié)果如下:
// ===> 處理前 <===
{
props: ['user-name'],
inject: ['id'],
directives: [function add() {}]
}
// ===> 處理后 <====
{
// 對(duì)象形式
props: {
userName: { // 轉(zhuǎn)換成駝峰命名
type: null
}
},
// 對(duì)象形式
inject: {
id: {
from: 'id'
}
},
directives: [{
// 對(duì)象形式
bind: function add() {}
update: function add() {}
}]
}
在校驗(yàn)完成之后,接下里就是正式的合并流程了,Vue針對(duì)每個(gè)規(guī)定的配置選項(xiàng)都有定義好的合并策略,例如data,component,mounted,methods等。如果Vue父子選項(xiàng)配置具有相應(yīng)的選項(xiàng),那么直接按照相應(yīng)的合并策略進(jìn)行合并。合并的入口如下:
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
...
// * 合并策略
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
// 根據(jù) key 獲取相應(yīng)的合并策略
const strat = strats[key] || defaultStrat
// 用相應(yīng)的合并策略進(jìn)行合并
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
以上通過(guò)兩個(gè)for循環(huán),遍歷parent和child的key(key這里指的是data/methods/created等),然后依次調(diào)用mergeField方法。mergeField則是通過(guò)key在strats中找到對(duì)應(yīng)的合并策略,然后用該合并策略進(jìn)行相應(yīng)合并。如果找不到合并策略,則使用默認(rèn)合并策略defaultStrat。
這里的strats已經(jīng)在該文件中定義,現(xiàn)在重點(diǎn)來(lái)看一下Vue中strats是如何定義合并策略的。
data合并
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
可以看出當(dāng)vm不存在時(shí),如果childVal即data不為函數(shù)形式,那么在非開(kāi)發(fā)環(huán)境下就會(huì)報(bào)錯(cuò),這也是為什么我們平時(shí)在寫(xiě)組件data時(shí)需要寫(xiě)成函數(shù)形式的原因。
但是這里的vm在什么情況下不存在呢?我們可以全局搜索一下mergeOptions(,看看哪些位置調(diào)用了該方法:

可以看出,在extend,mixin中,由于處理構(gòu)造函數(shù)階段時(shí),是沒(méi)有實(shí)例的,所以也就不會(huì)傳vm。這里我們主要討論extend。
接下來(lái)我們?nèi)炙阉饕幌?code>extend在哪些地方被調(diào)用了。

可以看到,extend方法主要在兩個(gè)位置被調(diào)用。
第一個(gè)位置在 src/core/global-api/assets.js文件
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
...
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// * 通過(guò)繼承,返回新的構(gòu)造函數(shù)(相當(dāng)于 子組件 的構(gòu)造函數(shù))
definition = this.options._base.extend(definition)
}
...
// * component / directive / filter
// * 將注冊(cè)的內(nèi)容全部添加到 Vue 構(gòu)造函數(shù)的 options 上
this.options[type + 's'][id] = definition
return definition
}
})
通過(guò)遍歷ASSET_TYPES,在Vue構(gòu)造函數(shù)上添加了component靜態(tài)屬性,即當(dāng)我們使用Vue.component的時(shí)候,實(shí)際上會(huì)執(zhí)行這里的this.options._base.extend(definition),即調(diào)用了extend方法來(lái)將傳入的組件選項(xiàng)合并后返回新的構(gòu)造函數(shù)。
第二個(gè)位置在src/core/vdom/create-component.js文件
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
...
// * 這里的 Ctor 有幾種形式
// * 1. 全局形式定義的 component,那么 Ctor 是構(gòu)造函數(shù)形式
// * 2. 局部定義的 component,那么是 對(duì)象形式。會(huì)對(duì)對(duì)象形式進(jìn)行 extend 處理
const baseCtor = context.$options._base
// 對(duì)象形式。會(huì)對(duì)對(duì)象形式進(jìn)行 extend 處理
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
...
return vnode
}
這段代碼在patch階段執(zhí)行的(僅做了解)。傳入的Ctor一種情況是全局定義的組件,此時(shí)Ctor通過(guò)extend創(chuàng)建,傳入的是構(gòu)造函數(shù)形式。另外一種情況則是局部注冊(cè)的組件,傳入的是選項(xiàng)配置形式,此時(shí)會(huì)執(zhí)行baseCtor.extend(Ctor),同樣會(huì)通過(guò)extend來(lái)創(chuàng)建構(gòu)造函數(shù)。
因此,無(wú)論是全局注冊(cè)的組件還是局部組件,最終都會(huì)調(diào)用extend方法,而extend方法在合并選項(xiàng)的時(shí)候會(huì)校驗(yàn)傳入的data是否是函數(shù)形式,這也就是為什么在定義組件時(shí)data必須是以函數(shù)形式定義。
現(xiàn)在我們已經(jīng)知道了組件data定義時(shí)必須使用函數(shù),但是為什么需要用函數(shù)定義呢,用普通的對(duì)象形式不行嗎?答案是不行。我們知道,Vue組件是可以復(fù)用的,也就是同一個(gè)extend出來(lái)的Vue構(gòu)造函數(shù)(也就是組件的構(gòu)造函數(shù)),可以被多次實(shí)例化,但是數(shù)據(jù)是不應(yīng)該被共享的。
如果data是對(duì)象形式,那么多個(gè)實(shí)例引用都是指向的同一個(gè)data對(duì)象。那么每次更改其中一個(gè)實(shí)例的data里的屬性時(shí),其他實(shí)例也會(huì)跟隨著改變。
如果data是函數(shù)形式,每次實(shí)例化時(shí)都會(huì)執(zhí)行該函數(shù),并且返回一個(gè)全新的data對(duì)象,這樣多個(gè)實(shí)例之間就各自有一份獨(dú)立的data了,從而解決數(shù)據(jù)被共享的問(wèn)題。
好了,了解完了data在組件中為什么要為函數(shù)形式后,我們繼續(xù)看data的后續(xù)合并過(guò)程。mergeDataOrFn函數(shù)執(zhí)行時(shí)最終調(diào)用的都是mergeData函數(shù):
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
// key 為 __ob__ 則跳過(guò)
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
// * 自身不存在這個(gè)key,那么使用將 from 的 key 和 value 添加到 to 上
// * 如果 to 原本是響應(yīng)式的,那么新增的 key 值也需要是響應(yīng)式的
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
// * 如果都是對(duì)象,繼續(xù)合并
mergeData(toVal, fromVal)
}
}
return to
}
這里的mergeData比較簡(jiǎn)單,實(shí)際上就是遞歸將兩個(gè)對(duì)象合并。需要注意的是,在合并的過(guò)程中,如果data是響應(yīng)式的,那么合并后添加的屬性也需要是響應(yīng)式的。
生命周期合并
生命周期的鉤子是在src/shared/constant.js中定義
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]
mergeHook是生命周期鉤子合并的策略,其核心是將父選項(xiàng)和子選項(xiàng)的對(duì)應(yīng)生命周期合并成數(shù)組形式,如果存在相同的生命周期執(zhí)行函數(shù),那么會(huì)進(jìn)行去重處理。
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
const res = childVal
? parentVal
// 都存在時(shí),拼接數(shù)組
? parentVal.concat(childVal)
: Array.isArray(childVal)
// parent 不存在時(shí)
? childVal
: [childVal]
// child 不存在,使用 parent
: parentVal
return res
? dedupeHooks(res)
: res
}
// hooks 去重
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
下面結(jié)合具體例子看看實(shí)際合并的結(jié)果:
const extend = {
created() {
console.log('extends')
}
}
const mixins = {
created() {
console.log('mixins')
}
}
// 父構(gòu)造函數(shù)
const Parent = Vue.extend({
created() {
console.log('parent created')
},
mixins: [mixins],
extends: extend,
})
// 子構(gòu)造函數(shù)
const Child = Parent.extend({
created() {
console.log('child')
},
mixins: [mixins],
extends: {
created() {
console.log('child extends')
}
}
})
new Child()
// extends
// mixins
// parent created
// child extends
// child
由于mixins里的created在合并時(shí)去重了,所以只會(huì)打印一遍mixins。另外可以看出,生命周期在執(zhí)行時(shí),parent和extends/mixins里的生命周期都是優(yōu)先于child生命周期執(zhí)行的。
components/filters/directives合并
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
}
}
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__。
這合并資源選項(xiàng)的時(shí)候,首先會(huì)創(chuàng)建一個(gè)原型指向父選項(xiàng)的空對(duì)象,再將子選項(xiàng)賦值給空對(duì)象。注意這里的父選項(xiàng)是通過(guò)原型鏈訪問(wèn),而子選項(xiàng)是直接添加到對(duì)象上的。例如:
Vue.component('test', {})
const vm = new Vue({
components: {
test: 'test'
}
})
console.log('vm.$options ==> ', vm.$options);
// 合并后,父類的 options 通過(guò) __proto__ 訪問(wèn)
{
components: {
test: "test",
__proto__: {
KeepAlive: { ... },
Transition: { ... },
TransitionGroup: { ... },
test: ...
}
},
directives: {},
filters: {},
_base: ...
}
這里的__proto__指向的就是復(fù)習(xí)選項(xiàng)的components。
watch合并
watch的策略是:
當(dāng)子選項(xiàng)不存在時(shí),使用父選項(xiàng);
當(dāng)父選項(xiàng)不存在時(shí),使用子選項(xiàng);
當(dāng)父選項(xiàng)和子選項(xiàng)都存在時(shí),如果他們具有相同的觀測(cè)字段,那么將其合并成數(shù)組形式。
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
...
// 子類不存在,使用父類
if (!childVal) return Object.create(parentVal || null)
...
// 父類不存在,使用子類
if (!parentVal) return childVal
const ret = {}
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
// 如果父類存在,改寫(xiě)成數(shù)組形式
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
// 拼接父類和子類
ret[key] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child]
}
return ret
}
props,methods,inject,computed合并
這一類的選項(xiàng)合并比較簡(jiǎn)單:
當(dāng)父選項(xiàng)不存在時(shí),使用子選項(xiàng);
當(dāng)子選項(xiàng)不存在時(shí),使用父選項(xiàng);
當(dāng)兩者都存在時(shí),使用子選項(xiàng)覆蓋父選項(xiàng)。
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
// 創(chuàng)建空對(duì)象
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
provide合并
使用的是mergeDataOrFn合并策略,同data合并。
總結(jié)
到這里我們就已經(jīng)對(duì)所有的合并策略都有所了解了??偨Y(jié)一下就是
data,provide,props,methods,inject,computed,``components,filters,directives基本都是在父子選項(xiàng)同時(shí)存在的情況下,子覆蓋父。生命周期在父子選項(xiàng)同時(shí)存在的情況下,會(huì)合并成數(shù)組形式,且去重。
watch在父子選項(xiàng)同時(shí)存在的情況下,會(huì)合并成數(shù)組形式,不去重。
Vue.extend的實(shí)現(xiàn)
Vue.extend的定義是在core/gloabl-api/extend.js文件里面,主要用于通過(guò)選項(xiàng)配參數(shù)生成新的構(gòu)造函數(shù)。這里的參數(shù)extendOptions就是我們?cè)诙x組件時(shí)傳入的配置選項(xiàng)。
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
// * 1. 查看配置選項(xiàng)中是否緩存有構(gòu)造函數(shù)
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
// * 2. 校驗(yàn)組件名稱
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
// * 3. 繼承
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
...
}
我們先看前半段代碼,前半段主要做了三件事:
檢驗(yàn)是否通過(guò)該選項(xiàng)配置生成過(guò)相應(yīng)構(gòu)造函數(shù),如果生成過(guò),那么直接使用生成的構(gòu)造函數(shù)即可。這里相當(dāng)于做了一層優(yōu)化。
校驗(yàn)組件名稱是否合法
通過(guò)原型實(shí)現(xiàn)繼承,生成新的構(gòu)造函數(shù)
接下來(lái)就是選項(xiàng)合并了
// * 4. 合并選項(xiàng)
Sub.options = mergeOptions(
Super.options,
extendOptions
)
參數(shù)Super.options就是Vue.options,前面已經(jīng)提過(guò)兩次,這里不再贅述了。這里合并后相當(dāng)于將Vue.options擴(kuò)充了,并將擴(kuò)充后的結(jié)果保存到Sub.options上(即新的構(gòu)造函數(shù)options上),所以在通過(guò)該構(gòu)造函數(shù)實(shí)例化的時(shí)候,擁有extendOptions配置的相關(guān)功能。
最后,會(huì)在新生成的構(gòu)造函數(shù)上添加一些靜態(tài)方法和屬性。注意這里的superOptioins/extendOptions/sealedOptioins都在resolveConstructorOptions方法尋找options中使用到。
// * 5. 添加 super
Sub['super'] = Super
...
// * 6. 添加一些方法
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
if (name) {
Sub.options.components[name] = Sub
}
// * 7. 添加一些屬性
// * 父選項(xiàng)
Sub.superOptions = Super.options
// * 傳入的配置選項(xiàng)
Sub.extendOptions = extendOptions
// * 合并后的配置相許那個(gè)
Sub.sealedOptions = extend({}, Sub.options)
最后總結(jié)來(lái)講,Vue.extend方法實(shí)際上就是通過(guò)原型繼承,并將Vue.options與extendOptions合并,從而實(shí)現(xiàn)一個(gè)新的構(gòu)造函數(shù)。
Vue.mixin的實(shí)現(xiàn)
Vue.mixin`方法的實(shí)現(xiàn)更是簡(jiǎn)單,打開(kāi)`core/global-api/mixin.js
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
不難看出,mixin實(shí)際上就是將兩個(gè)選項(xiàng)配置進(jìn)行合并。
結(jié)尾
在結(jié)尾之前,這里再拋出一個(gè)問(wèn)題,在Vue實(shí)例化過(guò)程中有一層判斷,當(dāng)_isComponent為true時(shí),將不會(huì)進(jìn)行選項(xiàng)合并,這是為什么呢?
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
這是因?yàn)楫?dāng)_isComponent為true時(shí),此時(shí)為組件渲染階段,options的內(nèi)容為
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
此時(shí)并沒(méi)有需要選項(xiàng)合并的項(xiàng),所以也就沒(méi)有必要mergeOptions了。
至此,選項(xiàng)合并的實(shí)現(xiàn)與應(yīng)用我們已經(jīng)學(xué)完了,下一章節(jié)將會(huì)學(xué)習(xí)Vue的核心模塊之一:響應(yīng)式系統(tǒng)。