前幾篇文章中我們講到了resolveConstructorOptions,它的重要功用是剖析當前實例組織函數上的options,不太邃曉的同硯們能夠看本系列的前幾篇文章。在剖析完其組織函數上的options以後,需要把組織函數上的options和實例化時傳入的options舉行兼并操縱並天生一個新的options。這個兼并操縱就是本日要講的mergeOptions。假如大家不想看死板的解說,能夠直接點擊大家都能懂的Vue源碼系列—04—mergeOptions-下,翻到文章末了,檢察全部mergeOptions的流程圖。
Merge two option objects into a new one.
Core utility used in both instantiation and inheritance.
先來看源碼中對mergeOptions要領的解釋。mergeOptions的功用是兼并兩個options對象,並天生一個新的對象。是實例化和繼承中運用的中心要領。可見mergeOptions要領的重要性。既然這麼重要,那我就帶大家一行一行的來剖析代碼。保證大家看完這篇文章后,能完全的明白mergeOptions的作用。
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child) // 搜檢組件稱號是不是正當
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
起首看傳入的三個參數,parent,child,vm,這三個參數離別代表的是該實例組織函數上的options,實例化時傳入的options,vm實例自身。連繫Vue作者寫的解釋,我們邃曉了,本來mergeoptions要領是要兼并組織函數和傳入的options這兩個對象。
邃曉了這點以後,接下來往下看
if (process.env.NODE_ENV !== 'production') {
checkComponents(child) // 搜檢組件稱號是不是正當
}
這段代碼主如果推斷當前環境是不是是臨盆環境,假如不是,則挪用checkComponents要領來搜檢組件稱號是不是是可用稱號。我們來看看checkComponents的邏輯。
function checkComponents (options: Object) {
for (const key in options.components) {
validateComponentName(key)
}
}
export function validateComponentName (name: string) {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}
假如child的options(實例化傳入的options)有components屬性。以下面這類狀況
const app = new Vue({
el: '#app',
...
components: {
childComponent
}
...
})
那末就挪用validateComponentName來考證傳入的組件稱號是不是相符以下特性。
- 包括数字,字母,下劃線,連接符,而且以字母開首
- 是不是和html標籤稱號或svg標籤稱號雷同
- 是不是和關鍵字稱號雷同,如undefined, infinity等
假如滿足第一條,而且第2,3條都是不雷同的話,那末組件稱號可用。
我們再回到mergeOptions源碼中
if (typeof child === 'function') {
child = child.options
}
假如child是function範例的話,我們取其options屬性作為child。
接下來看這三個要領以normalize開首的要領
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
這三個要領的功用相似,離別是把options中的props,inject,directives屬性轉換成對象的情勢。因為有些傳入的時刻能夠會是數組的情勢。如
Vue.component('blog-post', {
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
normalizeProps
我們先來看props處置懲罰的邏輯
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}
起首明白這兩個要領里的參數是什麼,options傳入的是child,即實例化時傳入的options。vm是實例。知道了這兩個參數是什麼,我們繼承來研討代碼。
const props = options.props
if (!props) return
const res = {}
let i, val, name
上面的代碼主如果聲明一些變量。res用來寄存修改后的props,末了把res賦給新的props。下面的邏輯能夠分為兩種狀況來斟酌
props是數組
當props是數組的時刻,以下面這類狀況
Vue.component('blog-post', {
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
它的處置懲罰邏輯是,遍歷props數組,把數組的每一項的值作為res對象的key,value值即是{type: null},即把上面例子中的[‘postTitle’]轉換成下面這類情勢
{
postTitle: { type: null }
}
props是對象
當props是對象時,以下面這類狀況
Vue.component('my-component', {
props: {
// 必填的字符串
propC: {
type: String,
required: true
}
}
})
這類狀況的處置懲罰邏輯是遍歷對象,先把對象的key值轉換成駝峰的情勢。然後再推斷對象的值,假如是純對象(即挪用object.prototype.toString要領的結果是[object Object]),則直接把對象的值賦值給res,假如不是,則把{ type: 對象的值}賦給res。終究上面這類情勢會轉換成
{
propC: {
type: String,
required: true
}
}
假如傳入的props不是純對象也不是數組,且當前環境也不是臨盆環境,則拋出正告。
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
末了,把處置懲罰過的props從新賦值給options.props。
normalizeInject
這個要領的邏輯和normalizeProps相似,主如果處置懲罰inject。inject屬性假如大家日常平凡不是寫庫或許插件的話,能夠很少接觸到,能夠先檢察inject的運用,inject的傳入和props相似。能夠傳入object,也能夠傳入array
// array
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
// object
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
因為這個要領和normalizeProps邏輯基礎一樣,這裏也不詳細睜開講了。上面的demo終究會被轉換成以下情勢
// array
{
foo: { from: 'foo'}
}
// object
{
foo: {
from: 'bar',
default: 'foo'
}
}
normalizeDirectives
這個要領主如果處置懲罰一些自定義指令,假如不相識自定義指令的同硯能夠自定義指令。這裏的要領處置懲罰邏輯重要針對自定義指令中函數簡寫的狀況。以下
Vue.directive('color', function (el, binding) {
el.style.backgroundColor = binding.value
})
normalizeDirectives組織函數會把這個指令傳入的參數,終究轉換成下面這類情勢
color: {
bind: function (el, binding) {
el.style.backgroundColor = binding.value
},
update: function (el, binding) {
el.style.backgroundColor = binding.value
}
}
因為文章篇幅所限,本篇文章先解說到這裏,下篇繼承帶大家來看mergeOptions的完成。