Vue之mergeOptions

new Vue(options)中首先处理的是下面一段代码, 这次就剖析这一段代码;

1
2
3
4
5
6
7
8
9
10
11
12
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}

初始化vm.$option

上面代码主要目的就是初始化vm.$option. 其中逻辑很简单,如果传入的option是一个组件,执行initInternalComponent, 否则执行mergeOptions;

initInternalComponent

initInternalComponent非常简单,就是以Vue.options为原型创建vm.$options, 并把options中的属性复制到vm.$options;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
opts.parent = options.parent
opts.propsData = options.propsData
opts._parentVnode = options._parentVnode
opts._parentListeners = options._parentListeners
opts._renderChildren = options._renderChildren
opts._componentTag = options._componentTag
opts._parentElm = options._parentElm
opts._refElm = options._refElm
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}

mergeOptions

mergeOptions接受三个参数,这个方法比较复杂; 首先接受三个参数

[parent, child, vm] = [resolveConstructorOptions(vm.constructor), options || {}, vm]

其中parent为resolveConstructorOptions(vm.constructor), 得到结果为Vue.options;child跟vm没什么说的;我们来看看mergeOptions做了什么;

具体代码在github这里; 主要逻辑是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
normalizeProps(child)
normalizeInject(child)
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)
}
}

其中normalizeProps/normalizeInject/normalizeDirectives主要是格式化对象;

normalizeProps主要是把options中的props格式化为如下对象 :

1
2
3
4
5
6
7
['bad-name', 'goodName'] // Array
// =>
{badName: {type: null}, goodName: {type: null}}
{badName: {type: 'String', default: 'hi'}, 'good-name': 'String'} // Object
// =>
{badName: {type: "String", default: "hi"}, goodName: {type: "String"}}

normalizeProps主要是把options中的inject格式化为如下对象:

1
2
3
4
5
6
7
['test', 'test1']
// =>
{test: {from: 'test'}, test1: {from: 'test1'}}
{test: 'hello', test1: {name: 'hi'}}
// =>
{test: {from: "hello"}, test1: {from: "test1", name: "hi"}}

normalizeDirectives主要是把options中的directives格式化为如下对象:

1
2
3
4
5
6
7
8
9
directives: {
hello: function() {console.log('hi')}, // function
hi: 'test'
}
// =>
directives: {
hello: {bind: function() {console.log('hi')}, update: function() {console.log('hi')} },
hi: 'test'
}

extend与mixins

接下来的代码其实是vue的混合用法,这里有官方文档-混合描述;
由于mixins与extend用法一致,只对mixins讲述:

1
2
3
4
5
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}

上面代码其实是递归调用mergeOptions, 并把mixins的内容递归处理到parent对象中;

接着初始化一个空对象{}这里我们记做obj, 并遍历parent里的key,mergeField到options中;

1
2
3
4
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}

mergeField是一个多态函数,根据传递的key值不同,parent与child合并的方式不同:

  • 如果key是el或者propsData时: obj[key] = child[key] === undefined ? parent[key] : child[key]
  • 如果key是data, 则递归的一层一层把所有的parent不存在的数据复制到child
  • 如果key是vue实例的生命周期, 则obj[key] = parent[key].concat(child[key])

    beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, destroyed, activated, deactivated

  • 如果key是components, directives, filters这些key值,则

    1
    2
    const res = Object.create(parent[ke] || null)
    return child[key] ? extend(res, child[ke]) : res
  • 如果key是watch,与生命周期类似

  • 如果key是prosp/methods/inject/computed时,与componets类似;

也即:

  1. key值为生命周期或者watch时,同名钩子函数将混合为一个数组,因此都将被调用。另外,混合对象的钩子将在组件自身钩子之前调用.
  2. 当为prosp/methods/inject/computed/components/directives/filters时,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。

如下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var mixin = {
created: function () {
console.log('混合对象的钩子被调用')
},
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
},
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
// => "混合对象的钩子被调用"
// => "组件钩子被调用"
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

总结

上面的所有步骤就是初始化传入的options中的一些对象, 并把混合的mixins和extend合并起来,最后赋值给vue实例的$options.