Re-learn Vue source code, according to Huang Yi big man Vue technology revealed, one by one, consolidate the Vue source knowledge points, after all, chewed up is their own, all the articles are synchronized in the public number (road in the front stack) and github.
The body of the
MergeOptions occur in two places: when code calls new Vue actively, and when creating a child component to call new Vue, they both execute the _init(options) method:
Let’s look at the logic of _init:
Vue.prototype._init = function (options? :Object) {
// merge options
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
)
}
// ...
}
Copy the code
Create a child component using initInternalComponent and one using mergeOptions. Let’s look at the second one:
Call the resolveConstructorOptions (vm) constructor), methods:
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
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
The Ctor argument passed here is Vue, so it has no super, and finally returns options for Vue.
So when you call mergeOptions, the first argument passed is the options of the large Vue, and the second options is the argument passed in the new Vue({}) (render, el, etc.). $options: SRC /core/util/options.js: SRC /core/util/options.js: SRC /core/util/options.js:
export function mergeOptions (
parent: Object,
child: Object, vm? : Component) :Object {
if(process.env.NODE_ENV ! = ='production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
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) } }function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Copy the code
It simply merges the parent and child parameters, recursively merging extends and mixins into the parent, then iterating over the parent, calling mergeField, then iterating over the Child, If the key is not on the parent, mergeField is called.
In mergeField, the strats method is called to get a different strat depending on the key passed in. If there is none, the defaultStrat is called:
const defaultStrat = function (parentVal: any, childVal: any) :any {
return childVal === undefined
? parentVal
: childVal
}
Copy the code
See that defaultStrat takes precedence over parent, followed by Child (a simple merge strategy). Following strats above, which essentially defines a number of merge strategies, strats begins with:
const strats = config.optionMergeStrategies
Copy the code
OptionMergeStrategies are defined as an empty Object: Object.create(null), which means that the strats are mainly used for extension, followed by the extension of strats properties such as data:
strats.data = function (parentVal: any, childVal: any, vm? : Component): ?Function {
if(! vm) {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
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
Copy the code
Component, filter:
export const 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) { process.env.NODE_ENV ! = ='production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
Copy the code
Here’s how the life cycles are merged:
export const LIFECYCLE_HOOKS = [
'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'
]
LIFECYCLE_HOOKS.forEach(hook= > {
strats[hook] = mergeHook
})
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function|?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
Copy the code
ParentVal and childVal can pass Function or Array
, and return Array
.
if(sub has) {if(the father) {returnMerge parent}else{
if(The children are arrays){returnThe child}else{
return[child]}}}else{
returnFather}Copy the code
Now that you know the logic of mergeOptions, take a look at its first argument: ResolveConstructorOptions, this returns the Vue options, the Vue options defined in SRC/core/global – API/index. The js:
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)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
// ASSET_TYPES are defined in constance.js
export const ASSET_TYPES = [
'component'.'directive'.'filter'
]
Copy the code
When Vue is initialized, an empty object, options, is defined. ASSET_TYPES is extended to this object, and builtInComponents are extended to some built-in components (transition, keepAlive). Add them all to vm.$options in _init.
And in the mixin module:
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this}}Copy the code
This is the process of merging global options. Similarly, mixin objects passed in globally are mixed into Vue options via mergeOptions. These are merged when executing new Vue.
Another merge occurs when a child component is initialized. Remember the component’s constructor procedure first:
/** * Class inheritance */
Vue.extend = function (extendOptions: Object) :Function {
// ...
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// ...
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// ...
return Sub
}
Copy the code
ExtendOptions corresponds to the component object defined earlier, which is merged with vue. options into sub. opitons.
Then recall the initialization of the component:
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
) :Component {
const options: InternalComponentOptions = {
_isComponent: true._parentVnode: vnode,
parent
}
// ...
return new vnode.componentOptions.Ctor(options)
}
Copy the code
Vnode.com ponentOptions. Ctor point of Vue. Extend the return value of the Sub, so in carrying out it, will then perform the subcomponents enclosing _init (options).
The merge now goes to the initInternalComponent method:
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Copy the code
Note that when it is called, the vm passed in is a subcomponent instance, so vm.constructive. options is the options of the subcomponent constructor, which was given at vue.extend. $options = object.create (sub.options); / / create(sub.options); / / create(sub.options); Vm.$options.__proto__ = Options for merging child component instances.
The parent VNode instance (parentVnode) and the parent Vue instance (parent) of the instantiated child are saved to VVM.$options, and other properties such as propsData from the parentVnode configuration are preserved.
Here’s an example to walk through the above process (focusing on how multiple created pieces are merged) :
import Vue from 'vue'
let childComp = {
template: '<div>{{msg}}</div>'.created() {
console.log('child created')},mounted() {
console.log('child mounted')},data() {
return {
msg: '123'
}
}
}
Vue.mixin({
created() {
console.log('parent created')}})let app = new Vue({
el: '#app'.render: h= > h(childComp)
})
Copy the code
Vue. Mixin (mergeOptions) {mergeOptions (parent) {mergeOptions (parent); Created on child, parenVal is not created on mergeHook (because new Vue({}) is not created), childVal is created on vue. mixin, [created(){}] returns a “created(){}]” property, which is a value of [created(){}].
The new Vue logic is then executed, and when _init is executed, it executes:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
Copy the code
The resolveConstructorOptions (vm) constructor) return is big Vue options, and then execute by mergeOptions parent is big Vue options, $options = “#app”; $options = “#app”; $options = “#app”;
After that, mergeOptions will continue, this time when vue.extend creates the child component constructor:
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Copy the code
Since the subcomponent constructor inherits the larger Vue, super. options is the options of the larger Vue. Merge it with the subcomponent’s custom object (childComp in this example), that is, the configuration defined by the subcomponent. So when mergeOptions is executed, the parent parameter is the options of the previous merged large Vue, and the child parameter is the configuration defined by the child component (childComp in this example). Note: Created on the child argument, created on the parent argument, and then executed on mergeHook:
if(sub has) {if(the father) {returnMerge parent}else{
if(The children are arrays){returnThe child}else{
return[child]}}}else{
returnFather}Copy the code
At this point, we go to merge the parent, which is parentvak.concat (childVal) in the code, which is the first parent and the second child.
MergeField then returns the options merged through mergeOptons, that is, the options of the subcomponent constructor (sub.options), Created (){}, created(){}].
At this point, the merge of the new Vue is complete, and the child component is initialized with this._init:
const Sub = function VueComponent (options) {
this._init(options)
}
Copy the code
Then execute again to:
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
)
}
Copy the code
InitInternalComponent is executed:
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
Copy the code
$options = vm. Constructive. options = vm.$options = vm.$options = vm. In this case, vm.$options will have a __proto__ attribute whose value is the contents of options.
$options assigns the parent vnode(options._parentvNode) and the parent Vue instance (options.parent) of the child component to vm.$options. $options = vm.$options = vm.
vm.$options = {
parent: Vue /* Parent Vue instance, options.parent*/.propsData: undefined._componentTag: undefined._parentVnode: VNode /* Parent VNode instance, options._parentVnode*/._renderChildren:undefined.__proto__: {
components: {},directives: {},filters: {},_base: function Vue(options) {
/ /...
},
_Ctor: {},
created: [
function created() {
console.log('parent created')},function created() {
console.log('child created')},mounted: [
function mounted() {
console.log('child mounted')},data() {
return {
msg: '123'}},template: '<div>{{msg}}</div>'}}Copy the code
conclusion
The new Vue is merged through mergeOption, and the component is merged through initInternalComponent, which is simpler to merge, so it merges faster.