Front we learned vue instantiation process, in which there is such a process mergeOptions (resolveConstructorOptions (vm) constructor), the options | | {}, vm). Let’s focus on mergeOptions today.
resolveConstructorOptions
Now let’s look at resolveConstructorOptions (vm) constructor), side into the reference for instance constructor
Take new Vue as an example, where the constructor is Vue
export function resolveConstructorOptions (Ctor: Class<Component>) {
// 这边主要就是返回Ctor.options
let options = Ctor.options
// Skip the super option changed case
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if(superOptions ! == cachedSuperOptions) {// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
Copy the code
Where is vue. options defined?
Options initialization can be found in initGlobalAPI in core/global-api/index.js
Vue.options = Object.create(null)
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type= > {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
Copy the code
Component, directive, filter, Vue.component.directive and vue.filter are used to register global resources. Options are injected with vue.component.directive and vue.filter.
Vue.extend
Careful friends here may find resolveConstructorOptions (vm) constructor), the vm. The constructor is not necessarily a Vue, sometimes is VueComponent, What is ctor. options?
Extend is called to register the component. The code is in core/global-api/ exten.js. We can look at some of the code
Vue.extend = function (extendOptions: Object) :Function {
extendOptions = extendOptions || {}
const Super = this
// ...
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// ...
return Sub
}
}
Copy the code
Sub is a new function, its prototype is inherited from super. prototype, and its static method options is derived from vue.options
So back to the above resolveConstructorOptions (vm) constructor), whether the vm Vue instance or VueComponent instance, are pointing to the Vue. The options
mergeOptions
The front analysis resolveConstructorOptions (vm) constructor) is returned to the Vue. The options, we now enter key mergeOptions today, it is located in the core/util/options/js
export function mergeOptions (
parent: Object,
child: Object, vm? : Component) :Object {
// Open environment verification component name
// Why only check child? Because parent is already validated
if(process.env.NODE_ENV ! = ='production') {
checkComponents(child)
}
// Compatible
if (typeof child === 'function') {
child = child.options
}
// There are several normalize for the Props Inject Directives configuration respectively
// For example, the properties of the Props are modified to the hump Directives function notation is formatted as an object
// You can check it out
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Merge the configuration of extends Mixins into parent
// Note that the strategy here is to merge the parent option first, not the child option
if(! child._base) {if (child.extends) {
// Note that this is a reassignment and does not affect the original object
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)
}
}
}
// Key points
// Define output values options={}
const options = {}
let key
// Merge options in parent
// If you are not careful on this side, it is easy to sink into the pit
// Note that mergeField is passed a key instead of a parent value
// Essentially merge the two
for (key in parent) {
mergeField(key)
}
// Merge only options in child
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
}
Copy the code
The overall flow of the mergeOptions function is fairly clear
-
Format props, inject, directives
-
Merges child extends, mixins to parent
-
Call mergeField to merge parent and child options
Merge strategy
MergeField calls different strats[key]() based on different merge attributes and passes in parent attribute values. Strats is the focus of our analysis, and we call it the policy object. Different key-values mean different merge policy functions for different attributes
// The initial value is usually empty object {}
const strats = config.optionMergeStrategies
Copy the code
Different policy functions are initialized for it in options.js
The default policy
Returns child options if there are, or parent options otherwise
const defaultStrat = function (parentVal: any, childVal: any) :any {
return childVal === undefined
? parentVal
: childVal
}
Copy the code
el/propsData
Throw a warning in the open environment and invoke the default merge policy
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)
}
}
Copy the code
lifeCycleHooks
Calling concat merges the lifecycle hook array and formats the subdata into an array, so the lifecycle passed through vue.mixin in q global is merged into the component lifecycle, called in turn
// LIFECYCLE_HOOKS = ['beforeCreate', 'created', 'beforeMount',
// 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed',
// 'activated', 'deactivated', 'errorCaptured', 'serverPrefetch']
LIFECYCLE_HOOKS.forEach(hook= > {
strats[hook] = mergeHook
})
// Call concat to merge arrays
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
}
// Delete duplicate hooks
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
}
Copy the code
assets
Returns the value of the merging of the child and parent, where the child overrides the parent
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
function mergeAssets (
parentVal: ?Object,
childVal: ?Object, vm? : Component, key: string) :Object {
const res = Object.create(parentVal || null)
if (childVal) {
AssertObjectType checks whether it is an object typeprocess.env.NODE_ENV ! = ='production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
Copy the code
props/methods/inject/computed
Similar to assets, returns the combined value of the child and parent, with the child overwriting the parent
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
}
Copy the code
watch
Concat merges arrays. Concat merges arrays. Concat formats arrays
strats.watch = function (
parentVal: ?Object,
childVal: ?Object, vm? : Component, key: string): ?Object {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
if(! childVal)return Object.create(parentVal || null)
if(process.env.NODE_ENV ! = ='production') {
assertObjectType(key, childVal, vm)
}
if(! parentVal)return childVal
const ret = {}
extend(ret, parentVal)
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
}
Copy the code
data
strats.data = function (parentVal: any, childVal: any, vm? : Component): ?Function {
if(! vm) {// data must be a function type
if (childVal && typeofchildVal ! = ='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
}
/ / call mergeDataOrFn
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
export function mergeDataOrFn (parentVal: any, childVal: any, vm? : Component): ?Function {
// Can be divided into cases with and without VMS
// The main difference is the binding of this in call
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 {
// Call the parent and child options separately
// Return a new function
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) {
// The final values are merged with mergeData
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
function mergeData (to: Object.from:?Object) :Object {
if (!from) return to
let key, toVal, fromVal
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
// Iterate over the parent option properties
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)) {// The suboption has no data and is assigned directly
set(to, key, fromVal)
} else if( toVal ! == fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) {// Attribute value recursive mergeData
mergeData(toVal, fromVal)
}
}
return to
}
Copy the code
Data’s merge strategy is a bit more complicated, so let’s summarize
-
Check if the data option is of a function type and if not, a warning is thrown
-
Calling mergeDataOrFn returns a new function in which the mergeData merge parent option is called
-
MergeData will recursively traverse the parent data, copying it into the child data
You can see that the merge of data is recursive
conclusion
This article focuses on the component instantiation _init, For the combination of the configuration options for the vm. The $options = mergeOptions (resolveConstructorOptions (vm) constructor), the options | | {}, vm) is how to do. The main analysis of mergeOptions implementation, for different attributes is to call different strategy function merge. The implementation of componentization will be examined later.