我们知道在new Vue时会传入一个参数对象,比如:
new Vue({
el: '#app',
data: {
name: 'zhangsan',
age: 20
},
components: {
HelloWorld
},
computed: {
getInfo () {
return `姓名: ${this.name}, 年龄: ${this.age}`
}
},
methods: {
getName () {
return this.name
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在实例化Vue实例时,会在源码中进行一次参数合并,在src/core/instance/init.js中的Vue.prototype._init方法中可以看到有mergeOptions这样一个方法,顾名思义就是配置合并
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ......
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
)
}
// ......
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
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
提示
需要注意的是,Vue实例的参数合并在Vue.prototype._init中,而组件实例的参数合并在Vue.extend中,后面组件部分会介绍
# resolveConstructorOptions
在合并参数之前,调用了resolveConstructorOptions方法,传递的参数为实例的constructor属性,我们知道实例的constructor属性会指向构造函数,所以实际上传递的就是Vue构造函数
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
// 先省略这部分代码,主要是用在实例化 Vue.extend 方法返回的构造函数,后续再介绍
// ......
}
return options
}
2
3
4
5
6
7
8
所以实际上resolveConstructorOptions方法返回的就是Vue.options,
在src/core/global-api/index.js中定义了很多Vue的属性,比如Vue.set,Vue.delete,Vue.nextTick...,不过我们先分析Vue.options
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
2
3
4
5
6
7
8
9
10
使用Object.create(null)方法创建一个没有原型链的对象,赋值给Vue.options,接着循环遍历常量ASSET_TYPES数组中的每一项,作为Vue.options中的属性,在src/shared/constants.js中可查看对ASSET_TYPES的定义
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
2
3
4
5
将Vue.options._base设置为对Vue的引用,并利用extend方法,将内置组件KeepAlive添加到Vue.options.components中,可在src/shared/util.js中查看extend工具函数的定义,在src/core/components/index.js中查看导出的内置组件对象KeepAlive
export function extend (to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
// src/core/components/index.js
import KeepAlive from './keep-alive'
export default {
KeepAlive
}
2
3
4
5
6
7
8
9
10
11
12
13
在src/platforms/web/runtime/index.js中也对Vue.options.directives和Vue.options.components进行了扩展
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
2
platformDirectives定义在src/platforms/web/runtime/directives/index.js中,主要是内置的指令v-model,v-show,platformComponents定义在src/platforms/web/runtime/components/index.js中,包含内置组件transition,transition-group
// src/platforms/web/runtime/components/index.js
import Transition from './transition'
import TransitionGroup from './transition-group'
export default {
Transition,
TransitionGroup
}
// src/platforms/web/runtime/directives/index.js
import model from './model'
import show from './show'
export default {
model,
show
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mergeOptions
在src/core/util/options.js中,可以看到mergeOptions方法的定义,在Vue.prototype._init中的mergeOptions方法调用中传递了三个参数Vue.options,options对象,和vm实例,分别对应mergeOptions的parent,child,vm三个形参,且vm参数可选
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child) // 验证options.components中的组件名称
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm) // 规范化props
normalizeInject(child, vm) // 规范化Inject
normalizeDirectives(child) // 规范化directives
// ......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
接着在mergeOptions中调用normalizeProps规范化props,normalizeProps方法和mergeOptions方法在同一文件中
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) // 将prop名称转为驼峰形式并返回
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
}
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
normalizeProps方法逻辑很简单,定义res对象作为规范后的props,遍历options.props中的每一项prop,并对每个prop名称使用camelize方法转换为驼峰形式,(例如:将data-info转换为dataInfo),然后添加到res对象中,然后将res对象重新赋值给options.props
接着调用normalizeInject方法和normalizeDirectives方法,逻辑都很简单明了,normalizeInject方法同normalizeProps方法思想一样,也是定义一个对象然后循环遍历重新赋值,这里就只分析下normalizeDirectives方法
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
2
3
4
5
6
7
8
9
10
11
normalizeDirectives方法将options.directives中的所有自定义指令循环遍历一次,做一次函数类型判断,如果是函数类型,则重新赋值为一个对象,并且默认包含bind和update,关于bind和update的使用可以查看官方文档自定义指令 (opens new window)部分
继续回到mergeOptions方法中
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
// ......
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
// ......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
接着做一层child._base判断,确保child为原始选项对象,而不是合并后的对象,因为只有parent参数对象和child参数对象合并过后才会有_base属性(上面说明过parent形参对应Vue.options,而Vue.options中存在_base属性,所以只有合并后才会存在_base属性)
如果参数对象child中含有extends和mixins属性,则会递归调用mergeOptions方法
继续看mergeOptions中的逻辑
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
循环遍历parent和child中的每个属性并且调用mergeField方法,在遍历child中时多了一层判断,只有当遍历的属性不存在与parent中时才会调用mergeField方法,避免重复调用,在mergeField中,会根据不同的参数key获取不同的合并策略,然后进行合并,如果未定义相关key的合并策略,则默认使用defaultStrat进行合并,parent._base使用的就是defaultStrat方法进行的合并
先简单看下defaultStrat合并方法
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
2
3
4
5
defaultStrat只是做了一层简单的判断
# strats(合并策略对象)
strats合并策略对象和mergeOptions方法都在一个文件中定义,strats指向config.optionMergeStrategies,config对象为src/core/config.js中的默认导出对象
// src/core/config.js
export default ({
optionMergeStrategies: Object.create(null),
// ......
}: Config)
// src/core/util/options.js
import config from '../config'
// ......
const strats = config.optionMergeStrategies
// ......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
strats中定义了很多合并策略,可以在src/core/util/options.js中详细查看,这里不一一说明,可以自行查看,逻辑都挺简单的,这里主要分析下data和props,watch的合并
# data
对data的合并时,先对vm做了层判断,看是否存在
注意
当vm为根实例时,mergeOptions的第三个参数vm是存在的,当为Vue.extend返回的构造函数的实例时,进行参数合并,vm参数没有传递
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
当vm不存在,则说明vm为Vue.extend返回的构造函数的实例,如果childVal类型不为function,则会报错,这就在源码层面解释了为什么组件中的data选项必须为一个function,接着都调用了mergeDataOrFn方法
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// Vue.extend返回的构造函数参数合并
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
// vue根实例的参数和并
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
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
35
36
37
38
39
40
41
42
43
mergeDataOrFn中同样是对vm做了层是否存在的判断,并且都返回一个合并函数,当为根实例的参数合并时,返回mergedInstanceDataFn方法,且在内部对childVal进行了function类型判断,说明new Vue的参数对象中的data既可以为返回一个对象的function,也可以直接为一个对象,当为Vue.extend返回的构造函数的实例的参数合并时,返回mergedDataFn方法,且他们都在返回的函数中调用了mergeData方法
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]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal)
}
}
return to
}
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
mergeData方法中对from参数对象中的每一项进行遍历并获取对应key的值,如果对应的key不存在于to对象中,则为to对象设置对应的key和值,如果存在,且在to和from中都为一个对象,则递归调用mergeData方法进行合并
综上就是对data参数的合并策略分析,单看代码,可能有点晦涩难懂,可以使用下列例子进行分析
Vue根实例的参数合并
const mixin = {
data: {
name: '张三',
info: {
age: 20,
addr: '四川省巴中市'
}
}
}
new Vue({
mixins: [mixin],
data: {
name: '李四',
info: {
sex: '男'
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
根据上述的源码分析,Vue.options会先和new Vue参数项的mixins数组中的每一项mixin进行合并,并将返回的对象和new Vue的中其余的参数项进行合并
Vue.extend返回的构造函数的实例的参数合并
const mixin = {
data () {
return {
name: '张三',
info: {
age: 20,
addr: '四川省巴中市'
}
}
}
}
new Vue.extend({
mixins: [mixin],
data () {
return {
name: '李四',
info: {
sex: '男'
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
需要注意的是Vue.extend参数对象中的data必须为function类型,同样的,Vue.options也会先和mixins中的每一项进行合并,再和其余项进行合并
# props
props、methods、computed都使用的相同的合并策略
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
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用Object.create(null)创建原型链为null的对象,利用对象中的相同的key后面的key的值会替换掉前面的key的值的原理进行合并
# watch
watch相对于props的合并会复杂一点,但也仅仅是相对!
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// ......
/* istanbul ignore if */
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal // 如果parentVal不存在,则直接返回childVal
const ret = {}
extend(ret, parentVal) // 将parentVal中的每一项都添加到ret中
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child]
}
return ret // 返回的ret对象中,每个key都对应一个
}
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
首先对childVal做是否存在判断,如果不存在则返回Object.create(parent || null)创建的对象,再利用assertObjectType对childVal做对象类型的断言判断,接着判断parentVal如果不存在,则直接返回childVal,如果条件都不成立则创建一个ret对象,将parentVal对象浅拷贝到ret中,再循环遍历childVal中的每个key,并根据遍历的key获取ret对象和childVal对象中的监听器,然后将每一项都重新设为数组,如果ret和childVal中都存在相同的监听器,则利用concat进行合并