Vue2.0 source code interpretation series (2)-open the merge option of vue mysterious gift box (3)

Vue2.0 source code interpretation series (2)-open the merge option of vue mysterious gift box (3)

Following the previous article on standardizing the merging of props, inject, directives, and the extends and mixins properties of the incoming options, this article will explain the core part of the mergeOptions function: the definition of the merge strategy.

Let's look at the last part of the source code of the mergeOptions function:

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) } Copy code

As you can see, the parent and child objects are traversed separately, and each key value is called

mergeField() function

mergeField() function
It is the final merger strategy function. Called inside
strat()
Function, and the strat function comes from
strats[key]
with
defaultStrat

Take a look

defaultStrat

/** * Default strategy. */ const defaultStrat = function ( parentVal: any, childVal: any ): any { return childVal === undefined ? parentVal : childVal } Copy code

and so

defaultStrat
The logic is that if the attribute value on the child exists, the attribute value on the child is taken, and if it does not exist, the attribute value on the parent is taken. The option configuration on child is preferred.

Look again

strats[key]

strats is a function

const strats = config.optionMergeStrategies export type Config = { //user optionMergeStrategies : {[key: string]: Function }; ... }; Copy code

You can define keys of different function types on starts (config.optionMergeStrategies), and then define the merge strategy corresponding to these keys.

config.optionMergeStrategies.el, config.optionMergeStrategies.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) } } Copy code

As can be seen from the above code, the merging strategy of el and propsData is the default merging strategy. If the option of the child component exists, the child component is taken, otherwise the parent component is taken.

####config.optionMergeStrategies.data, config.optionMergeStrategies.provide

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) } strats.provide = mergeDataOrFn Copy code

As you can see, the data and provide merge strategy are called

mergeDataOrFn
Function; The following analyzes the merge strategy of the data option, and provide is the same.

  • If it is not the current instance, the instance created by Vue.extend()
    • If childVal is not a function, return parentVal as the result of the current data merge
    • Otherwise call
      mergeDataOrFn(parentVal, childVal)
      , Vm instance is not passed in at this time
  • If it is the current instance, the instance created by new Vue()
    • transfer
      mergeDataOrFn(parentVal, childVal, vm)
      , At this time, the vm instance is passed in

Find the mergeDataFn function block

/** * Data */ 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 } } } } Copy code

According to the above analysis, we will soon be able to find:

  • If the current instance of vm is not passed in, that is when the instance is created by Vue.extend()/Vue.component
    • If there is no childVal and parentVal, then parentVal is returned
    • If there is no parentVal and childVal, then childVal is returned
    • If both childVal and parentVal have data options, call
      mergeData()
      Function merge data option
  • If a vm instance is passed in, it is an instance created in the form of new Vue()
    • If the data option is passed in when creating a new instance, call
      mergeData
      The data option on the function merge instance and constructor
    • If the data option is not passed in when creating a new instance, the data option on the constructor is returned

No matter which way the instance is created, it will eventually be called

mergeData
The function merges data options. Let's take a look
mergeData
function

/** * Helper that recursively merges two data objects together. */ 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 } Copy code
  • If there is no data option on the constructor (from), return the data option on the instance (to)
  • If there is also a data option on the constructor
    • First return all the key values (keys) on the data of the constructor from
    • Traverse the keys of the constructor data
    • If there is no key value on the data option of the constructor on the data option of the instance to, call the set method to link the (key, fromVal) key-value pair to the data option of the instance object to
    • Otherwise, if the data option of to has the same key value as the data option on the constructor, and the value corresponding to the key is an object, the mergeData function is called recursively
    • Finally, return to the data option on the instance to

####Hook function merging strategy

export const LIFECYCLE_HOOKS = [ 'beforeCreate' , 'created' , 'beforeMount' , 'mounted' , 'beforeUpdate' , 'updated' , 'beforeDestroy' , 'destroyed' , 'activated' , 'deactivated' , 'errorCaptured' , ' serverPrefetch' ] /** * Hooks and props are merged as 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 } 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 }) Copy code

We see that each hook merge calls the mergeHook function, the logic of mergeHook

  • Get the hook array res
    • If this hook does not exist on child options and parent exists, then the hook on parent is returned
    • If the same hook exists on both child and parent, the attributes after concat are returned
    • If the child options exist, but the parent does not exist, then the attribute on the child is judged to be an array, and the attribute on the child is directly returned
  • If res does not exist, return res
  • Otherwise, return dedupeHooks(res), the hooks after deduplication

####Assets (components, directives, filters) merger strategy

/** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */ 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 } } export const ASSET_TYPES = [ 'component' , 'directive' , 'filter' ] ASSET_TYPES.forEach( function ( type ) { strats[type + 's' ] = mergeAssets }) Copy code

Processing logic: If these options exist on the child, they are merged with the options on the parent of the constructor, otherwise, these types options on the constructor are returned

####props/methods/inject/computed merge strategy

/** * Other object hashes. */ 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 code

The logic of this merge method is also very simple:

  • If there is no such option on the constructor, directly return the option on the instance
  • If there is on the constructor and also on the instance, return the merged result

####watch's merger strategy

/** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as 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 code

There are also three types of logic processing:

  • If there is no watch option on the instance, return the watch option on the constructor parent or null
  • Otherwise, if there is on the instance but not on the constructor, return the watch option on the instance
  • Otherwise, when there are both, traverse the watch object on the instance
    • If there is a key on the parent and watch in the child
      • If the value corresponding to the key on the parent is not an array, then return [parent]
      • Combine the watch options of parent and child
    • If the key of the watch in the child does not exist on the parent, return the child in the form of an array

Finally finished the analysis of the merge options (three articles). It s not easy to take time out of bed every day to nibble on this piece. It is not easy to promote your own growth. I hope it can also bring some inspiration to the little cuties. If there is a place to say no Clearly welcome questions and discussions in the comment area below~

Seeing the little cuties here, please give me a thumbs up and encourage me to continue creating. It is not easy to create, learn together, and realize the freedom of wealth as soon as possible~