前几篇文章中咱们讲到了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的实现。