我们知道在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
进行合并