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

  1. Format props, inject, directives

  2. Merges child extends, mixins to parent

  3. 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

  1. Check if the data option is of a function type and if not, a warning is thrown

  2. Calling mergeDataOrFn returns a new function in which the mergeData merge parent option is called

  3. 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.