文章目录
Vue2 源码解读一:Global API
Vue2 源码解读二:mergeOptions
1、入口方法
2、检查组件名称的合法性
3、数据规范化、格式化
4、合并策略
Vue2 源码解读三:Vue实例
Vue2 源码解读四:Observer模块
Vue2 源码解读二:mergeOptions
mergeOptions是Vue的源码中常用的合并策略,对Vue的data、methods、computed、watch、lifecycle等等的合并。在Vue实例化、Vue.extend、Vue.mixin外部调用方法中都有调用。
1、入口方法
该方法可分为以下几个步骤:
- 检查组件名称的合法性
checkComponents
- 数据规范化、格式化
normalizeProps()、normalizeInject()、normalizeDirectives()
- 处理extend、mixin
child.extends、child.mixins
- 合并字段
mergeField
,按照不同的属性采用不同的合并策略
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
* 合并两个options对象,并返回一个新的options
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
// 检查组件名称的合法性
checkComponents(child)
}
// 兼容处理child为function的情况
if (typeof child === 'function') {
child = child.options
}
// 规范化、格式化Props
normalizeProps(child, vm)
// 规范化、格式化Inject
normalizeInject(child, vm)
// 规范化、格式化Directives
normalizeDirectives(child)
// 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.
// _base来自于Vue初始化
if (!child._base) {
// 处理extends
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
// 处理mixins
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
// 把parent合并到options
for (key in parent) {
mergeField(key)
}
// 再把child合并到options
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、检查组件名称的合法性
使用正则判断组件名称的合法性,然后再判断是否为内置标签。
/**
* Validate component names
*/
function checkComponents (options: Object) {
// 检查每一个组件的名称
for (const key in options.components) {
validateComponentName(key)
}
}
export function validateComponentName (name: string) {
// 正则校验
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
)
}
// 是否为内置标签,比如slot、component
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
3、数据规范化、格式化
把props、inject、directives转成标准格式的对象。
/**
* Ensure all props option syntax are normalized into the
* Object-based format.
* 格式化props,确保每个都是标准格式的对象
*/
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
// 没有则不处理
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
// 如果prop是数组,则循环处理每一个prop
i = props.length
while (i--) {
val = props[i]
// props数组里面必须是string
if (typeof val === 'string') {
// “大驼峰”命名
name = camelize(val)
// 转成含有type字段的对象
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)
// 处理 只指定类型的prop
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
}
/**
* Normalize all injections into Object-based format
* 格式化injections,确保每个都是标准格式的对象
*/
function normalizeInject (options: Object, vm: ?Component) {
const inject = options.inject
// 没有则不处理
if (!inject) return
// options.inject,在初始化的时候,已响应式,修改normalized,会同步修改options.inject
const normalized = options.inject = {}
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
// 转成含有from字段的对象
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
for (const key in inject) {
const val = inject[key]
// 转成含有from字段的对象
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
/**
* Normalize raw function directives into object format.
* 格式化injections,确保每个都是标准格式的对象
*/
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
if (typeof def === 'function') {
// 转成包含bind、update属性的对象
dirs[key] = { bind: def, update: def }
}
}
}
}
4、合并策略
Vue针对不同属性定义不同的合并策略,所有的策略方法基本都包含四个参数:parentVal,childVal,vm,key。
- 默认的合并策略:优先返回后者child,没有再返回前者parent
- el和propsData的合并策略:使用默认的合并策略
- data和provide的合并策略:如果data是一个function,则调用,使之返回一个对象。然后再合并递归data对象中的每一个字段,新字段添加监听属性。相同属性使用前者to,抛弃后者from。最后返回一个function。
- 钩子函数的合并策略:使用数组装载相同的钩子函数,并消除重复的钩子函数,后面按序触发调用
- component、filter、directive的合并策略:采用后者childVal,前者parentVal放在_proto_属性中。
- watch的合并策略:非覆盖,数组合并
- props、methods、inject、computed的合并策略:相同属性,后者childVal覆盖前者parentVal
/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*
* option覆盖策略
*/
const strats = config.optionMergeStrategies
/**
* Options with restrictions
* el和propsData的合并策略,使用默认的策略
*/
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
`option "${key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
}
}
/**
* Helper that recursively merges two data objects together.
* 合并data,把from对象合并到to对象,并返回to对象
*/
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
// 获取from的所有key(包括Symbol)
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...
// 跳过__ob__属性
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
// 如果to不包含key,则把from的值给to
set(to, key, fromVal)
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
// 如果fromVal和toVal都是对象,那么递归合并
mergeData(toVal, fromVal)
}
}
return to
}
/**
* Data
* 合并data,data可能是对象,也可能是function
*/
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// 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 {
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
}
}
}
}
// Vue中data的合并策略
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)
}
/**
* Hooks and props are merged as arrays.
* 合并钩子函数,比如:created、mounted、beforeDestroy,非覆盖,数组合并
*/
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
return res
? dedupeHooks(res)
: res
}
// 消除重复的钩子函数
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
// 钩子函数的合并策略
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
/**
* Assets
*
* When a vm is present (instance creation), we need to do
* a three-way merge between constructor options, instance
* options and parent options.
*
* 合并component、filter、directive
*/
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
// 使用parent的_proto_,或者空对象
const res = Object.create(parentVal || null)
if (childVal) {
// 只能是对象
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
// 对象属性覆盖处理
return extend(res, childVal)
} else {
return res
}
}
// component、filter、directive 合并策略
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
/**
* Watchers.
*
* Watchers hashes should not overwrite one
* another, so we merge them as arrays.
*
* watch的合并策略,非覆盖,数组合并
*/
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// work around Firefox's Object.prototype.watch...
// 火狐浏览器的Object.prototype.watch兼容处理
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
// 如果childVal不存在,则返回parentVal的_proto_的空对象
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
// 如果parentVal不存在,则返回childVal
if (!parentVal) return childVal
const ret = {}
// 复制一份parentVal
extend(ret, parentVal)
// 数组化,有则concat,没有则创建。
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
}
/**
* Other object hashes.
*
* props、methods、inject、computed的合并策略
*/
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// childVal必须是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
}
// provide 和data的合并策略一样
strats.provide = mergeDataOrFn
/**
* Default strategy.
* 默认策略,优先child,在parent
*/
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}