前幾篇文章中咱們講到了resolveConstructorOptions,它的主要功能是解析當前實例構造函數上的options,不太明白的同窗們能夠看本系列的前幾篇文章。在解析完其構造函數上的options以後,須要把構造函數上的options和實例化時傳入的options進行合併操做並生成一個新的options。這個合併操做就是今天要講的mergeOptions。若是你們不想看枯燥的講解,能夠直接點擊人人都能懂的Vue源碼系列—04—mergeOptions-下,翻到文章最後,查看整個mergeOptions的流程圖。html
Merge two option objects into a new one.
Core utility used in both instantiation and inheritance.
先來看源碼中對mergeOptions方法的註釋。mergeOptions的功能是合併兩個options對象,並生成一個新的對象。是實例化和繼承中使用的核心方法。可見mergeOptions方法的重要性。既然這麼重要,那我就帶你們一行一行的來解析代碼。保證你們看完這篇文章後,能完全的理解mergeOptions的做用。vue
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這兩個對象。
明白了這點以後,接下來往下看segmentfault
if (process.env.NODE_ENV !== 'production') { checkComponents(child) // 檢查組件名稱是否合法 }
這段代碼主要是判斷當前環境是否是生產環境,若是不是,則調用checkComponents方法來檢查組件名稱是不是可用名稱。咱們來看看checkComponents的邏輯。api
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來驗證傳入的組件名稱是否符合如下特徵。app
若是知足第一條,而且第2,3條都是不相同的話,那麼組件名稱可用。
咱們再回到mergeOptions源碼中ide
if (typeof child === 'function') { child = child.options }
若是child是function類型的話,咱們取其options屬性做爲child。
接下來看這三個方法以normalize開頭的方法svg
normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child)
這三個方法的功能相似,分別是把options中的props,inject,directives屬性轉換成對象的形式。由於有些傳入的時候可能會是數組的形式。如函數
Vue.component('blog-post', { props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })
咱們先來看props處理的邏輯post
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是數組的時候,以下面這種狀況
Vue.component('blog-post', { props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })
它的處理邏輯是,遍歷props數組,把數組的每一項的值做爲res對象的key,value值等於{type: null},即把上面例子中的['postTitle']轉換成下面這種形式
{ postTitle: { type: null } }
當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。
這個方法的邏輯和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' } }
這個方法主要是處理一些自定義指令,若是不瞭解自定義指令的同窗能夠自定義指令。這裏的方法處理邏輯主要針對自定義指令中函數簡寫的狀況。以下
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的實現。