Vue source code analysis -- option merging strategy analysis

The article was first published on personal blog Small gray space

Option merge policy analysis

After converting the three options of projects input directive into a unified format, you can start to merge the options. Take a look at the code of option merging

// core/instance/init.js
// Start merge option
const options = {}
let key
for (key in parent) {
  // First, merge the options in the parent instance into the target object
  mergeField(key)
}
for (key in child) {
  // Traverse the child. If the attribute in the child does not exist on the parent itself, combine the attribute into the parent
  if (!hasOwn(parent, key)) {
    // 
    mergeField(key)
  }
}
function mergeField (key) {
  // The combination policies of various options are defined on the starts object
  const strat = strats[key] || defaultStrat
  options[key] = strat(parent[key], child[key], vm, key)
}

When merging options, the user-defined merge policy is preferred. If the user-defined option policy does not exist, the default merge policy is used. Each key in the starts object corresponds to the merge strategy of one option

Default merge policy

The default option consolidation policy. If the subclass option exists, the subclass option is used to override the parent option

const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

el option merge policy

The el option provides a DOM element that already exists on the page as the mount target of the Vue instance. The change option only exists on Vue instances, and the el option is not allowed on other subclass constructors

if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    // Only Vue instances have the el option, and other subclass constructors are not allowed to have the el option
    if (!vm) {
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    return defaultStrat(parent, child)
  }
}

data option merge policy

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {  // Ensure that the data type of the subclass must be a function rather than an object. Using an object to return a data type can realize service reuse, and the data between component instances will not affect each other
      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 cmergeDataOrFn merge option
    return cmergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    // in a Vue.extend merge, both should be functions
    // The options passed in are those in the component, so there is no vm instance·
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    return function mergedDataFn () {
      // The data option returns a function when the parent and child classes exist at the same time, and calls the real merge option of the method when the responsive system initializes later
      return mergeData(
        // Pass the returned results of the data function of the subclass instance and the parent instance to mergerData for merging
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    // The data attribute hung in the Vue instance, which can be an object
    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) {
        // Merge the options of the instance with the options in the Vue constructor
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

By analyzing the above code, it can be found that during Vue instance initialization, the data option does not really merge, but only returns a function, which returns the execution result of mergeData, that is, the formal merging of data options
Is in the mergeData function. Let's take a look at the implementation of the mergeData function

function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal

  // Through reflect Ownkeys can get the Symbol attribute
  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)) {
      // The parent class option does not exist in the subclass. Add the parent class option to the subclass and make it responsive
      set(to, key, fromVal)
    } else if (
      // Parent class options already exist in subclasses, are not equal, and are all ordinary objects. Recursion is required
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

By analyzing the mergeData method, it is found that the merging principle of data options is to merge the options of the parent class into the sub class. If there is a conflict between the options of the parent class and the sub class (for example, there are object attributes, inconsistent data types or different values), the options of the sub class will be retained.
If the options are nested, recursive calls are required for merging.

Knowledge points

The Symbol type introduced in ES6 cannot be enumerated when it is used as an object attribute, and object Getownpropertynames() also does not return the properties of the Symbol object, but you can use object Getownpropertysymbols() to get the Symbol property
In the mergeData method above, use reflect The ownkeys method can obtain all attributes including the Symbol object attribute. Its return value is equal to its return value, which is equal to object getOwnPropertyNames(target). concat(Object.getOwnPropertySymbols(target)).

Finally, in development, when creating Vue instances, the data option in the options provided can be an object, while when creating components, the data option must be a function. It can be understood that the purpose of creating components is to realize service reuse. When data is used as a function, in
When creating multiple component instances, the data between component instances will not be applied to each other, because the data of each component instance is a copy of the data in the component template.

Vue constructor built-in option merge

Vue constructor has built-in options of components, directive and filter. Both Vue root instance and component instance need to be combined with these options.

// core/util/options.js
// Vue default options are merged into each Vue instance
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) // Create a 🈳 Object to make its prototype point to the resource options of the parent class. The built-in components, instructions and filters need to be called by prototype
  if (childVal) {
    // Verify the validity of options in the development environment. These options need to be an object
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}

The merging strategy of these options is also relatively simple. First, create an empty object, and the prototype of the control object points to the resource options of the parent class, and then assign the options of the child class to the whole empty object

Keywords: Javascript Vue.js

Added by xhitandrun on Mon, 21 Feb 2022 16:30:42 +0200