1.5 Merge Policies

One of the difficulties with merging strategies is that there are many types of merging options, and the rules vary from one option to another. The main ideas are as follows:

  1. VueThere is a defined merge strategy for each specified option, for exampledata,component,mountedAnd so on. If the merged child and parent configurations have the same options, you only need to merge options according to the specified policy.
  2. Due to theVueThe options passed are open, and there are cases where the options passed do not have custom options. At this time, because there is no default merge policy for options, the principle of handling is that if there is a subclass configuration option, the subclass configuration option is used by default, and if there is no parent class configuration option, the parent class configuration option is selected.

We use these two ideas to analyze the implementation of the source code, first look at mergeOptions in addition to the specification of the logic after testing.

function mergeOptions ( parent, child, vm ) {...var options = {};
  var key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField (key) {
    // If there is a custom option policy, use the custom option policy; otherwise, use the default policy.
    var strat = strats[key] || defaultStrat; 
    options[key] = strat(parent[key], child[key], vm, key);
  }

  return options
}
Copy the code

twoforThe loop specifies the order of the merge, with the custom option policy taking precedence and the default policy being used if there is none. whilestratsUnder eachkeyThis corresponds to the combined strategy for each particular option

1.5.1 Default Policy

We can define the behavior of instances with a wealth of options, which can be roughly categorized as follows:

  1. withdata,props,computedOptions such as define instance data
  2. withmounted, created, destoryedEtc define the life cycle function
  3. withcomponentsCertified components
  4. withmethodsOption defines instance methods

Of course, such as watch, inject, directives, such as filter options, to sum up, the Vue provide configuration item is rich. In addition, we can also use options that do not have a default configuration policy. A typical example is the introduction of Vuex for state management and vuE-Router for companion routes:

new Vue({
  store, // vuex
  router// vue-router
})
Copy the code

Whether it’s a plug-in or a user-defined option, their merge strategy follows the second point of the thinking: ** if the child configuration exists, take the child configuration, if the parent configuration does not exist, use the child to override the parent. ** It is described in defaultStrat.

// User-defined option policy
var defaultStrat = function (parentVal, childVal) {
  // If the child does not exist, use the parent; if the child does exist, use the child configuration
  return childVal === undefined
    ? parentVal
    : childVal
};
Copy the code

Next, I will enter into the analysis of some specific merger strategies, which can be roughly divided into five categories:

1. General option merge

2. Merge built-in resource options

3. Lifecycle hook merge

4. watchOption to merge

5. props,methods, inject, computedSimilar option merge

1.6 Consolidation of general options

1.6.1 MERGE of EL

El provides a DOM element that already exists on the page as the mount target of the Vue instance, so it exists only when the Vue instance is created. The EL option cannot be defined in a subclass or subcomponent, so the MERGE strategy of EL is to use the default strategy to merge while ensuring that the option exists only in the root Vue instance.

strats.el = function (parent, child, vm, key) {
  if(! vm) {// Only vue instances are allowed to have el attributes. Other subclass constructors are not allowed to have EL attributes
    warn(
      "option \"" + key + "\" can only be used during instance " +
      'creation with the `new` keyword.'
    );
  }
  // Default policy
  return defaultStrat(parent, child)
};

Copy the code

1.6.2 data merging

The general option focuses on merging data. After reading this section of the source code, you may be able to resolve the confusion in your mind as to why data is passed an object when vUE creates an instance, but can only pass a function when a component is defined internally.

// Merge data
strats.data = function (parentVal, childVal, vm) {
  // vm represents whether the instance is created for Vue or not
  if(! vm) {if (childVal && typeofchildVal ! = ='function') { // The subclass's data type must be a function and not an object
      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); // The vue instance needs to pass the VM as the third argument to the function
};
Copy the code

The data policy ultimately calls the mergeDataOrFn method, depending on whether the current VM is an instance or simply a child parent. If the relationship is child and parent, the data option needs to be formally verified to ensure that its type is a function and not an object.

function mergeDataOrFn ( parentVal, childVal, vm ) {
  / / child parent class
  if(! vm) {if(! childVal) {// Subclass does not have data option, merge result to parent class data option
      return parentVal
    }
    if(! parentVal) {// If there is no data option in the parent class, merge result to subclass data option
      return childVal
    }
    return function mergedDataFn () { // The data option returns a function if both parent and subclass exist
      // Subclass instance and superclass instance, pass the object returned by the data function in the subclass and superclass instance to mergeData for data merging
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this.this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this.this) : parentVal
      )
    }
  } else {
  / / the Vue instance
    // Vue constructor instance object
    return function mergedInstanceDataFn () {
      var instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal;
      var defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal;
      if (instanceData) {
        // When the data option is passed in the instance, merge the instance's data object with the data attribute option on the Vm constructor
        return mergeData(instanceData, defaultData)
      } else {
        // When data is not passed in the instance, the data attribute option on the Vm constructor is returned by default
        return defaultData
      }
    }
  }
}
Copy the code

From the implementation of the source code, the merge of data is not a simple merge of two data objects, but directly return a mergedDataFn or mergedInstanceDataFn function, and the real merge opportunity is in the follow-up initialization of the data response system link, The first step in initializing a data-responsive system is to get the merged data, which is to execute the mergeData logic. (Refer to the following sections for building responsive systems.)

function mergeData (to, from) {
  if (!from) { return to }
  var key, toVal, fromVal;
  // Reflect. OwnKeys can get the Symbol property
  var keys = hasSymbol
    ? Reflect.ownKeys(from)
    : Object.keys(from);

  for (var i = 0; i < keys.length; i++) {
    key = keys[i];
    toVal = to[key];
    fromVal = from[key];
    if(! hasOwn(to, key)) {// If the child has no parent, add the new data to the responsive system.
      set(to, key, fromVal); 
    } else if( toVal ! == fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) {// When the merged data is a multi-layer nested object, you need to recursively call mergeData to compare and mergemergeData(toVal, fromVal); }}return to
}
Copy the code

The two parameters of mergeData method are the result of the parent data option and the child data option, that is, two data objects. From the source code, the principle of data merging is to integrate the data of the parent class into the data option of the child class. If the data of the parent class conflicts with the data of the child class, the data of the child class is retained. If the object is deeply nested, you need to recursively call mergeData to merge the data.

Finally, why is data of a Vue component a function rather than an object? I think it can be explained as follows: components are designed for reuse by creating functions that generate the equivalent of one copy of data in a separate memory space ata time, so that the data between each component does not affect each other.

1.7 Merging Built-in Resource options

In 1.2, we saw that Vue has several options by default, namely components, directive, and filter. All root instances and parent instances need to be combined with the system’s own resource options. It is defined as follows:

// Resource options
var ASSET_TYPES = [
  'component'.'directive'.'filter'
];

// Define the resource merge policy
ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets; // Define the default policy
});

Copy the code

The merge logic for these resource options is simple. A prototype is created to point to the empty object of the parent resource option, and the subclass option is assigned to the empty object.

// Resource options customize merge policies
function mergeAssets (parentVal,childVal,vm,key) {
  var res = Object.create(parentVal || null); // Create an empty object whose prototype points to the resource options of the parent class.
  if (childVal) {
    assertObjectType(key, childVal, vm); / / components, filters, directives option must be for the object
    return extend(res, childVal) // Subclass options are assigned to empty objects
  } else {
    return res
  }
}
Copy the code

With the following example, let’s look at the result of the specific merge:

var vm = new Vue({
  components: {
    componentA: {}},directives: {
    'v-boom': {}}})console.log(vm.$options.components)
// The result of combining the root instance options with the resource default options
{
  components: {
    componentA: {},
    __proto__: {
      KeepAlive: {}
      Transition: {}
      TransitionGroup: {}
    } 
  },
  directives: {
    'v-boom': {},
    __proto__: {
      'v-show': {},
      'v-model': {}}}}Copy the code

To summarize, for resource options such as directives, filters, and components, the parent class option will be handled as a prototype chain. Directives Subclasses must go through the stereotype chain to find and use built-in components and built-in directives.

1.8 Merging of lifecycle hook functions

There is an important idea when learning Vue, the lifecycle. It is the basis for efficient component development using Vue. We can define the functions that need to be executed at different stages of the component instance to enrich the function of the component. Before introducing option merging of lifecycle hook functions, it’s worth reviewing the following official lifecycle diagram.

However, we can see from the source that Vue has more lifecycle hooks, as many as 12, the execution timing of each hook will not be discussed, they will appear in later chapters. We are interested in how the lifecycle hook functions of the child and parent components are merged.

var LIFECYCLE_HOOKS = [
  'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'.'serverPrefetch'
];
LIFECYCLE_HOOKS.forEach(function (hook) {
  strats[hook] = mergeHook; // Execute mergeHook on all merges of lifecycle hook options
});
Copy the code

MergeHook is a lifecycle hook merge strategy. It simply summarizes the code. The hook function merge principle is:

  1. If both subclasses and superclasses have the same hook option, merge the subclass option and the superclass option.
  2. If the parent class does not have the hook option, the subclass does, and returns the subclass’s hook option as an array.
  3. When a subclass does not have a hook option, it returns the superclass option.
  4. The subclass option is placed at the end of the array so that the parent option is always executed before the subclass option when the hook is executed.
// Lifecycle hook options merge policies
function mergeHook (parentVal, childVal) {
    // 1. If the subclass and its parent have hook options, merge the subclass and parent options.
    // 2. If the parent class does not have the hook option, return the subclass hook option as an array if the subclass does.
    // 3. If a subclass does not have a hook option, the parent class option is returned.
    var res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal; 
    return res
      ? dedupeHooks(res)
      : res
  }
// Prevent multiple component instance hook options from influencing each other
  function dedupeHooks (hooks) {
    var res = [];
    for (var i = 0; i < hooks.length; i++) {
      if (res.indexOf(hooks[i]) === - 1) { res.push(hooks[i]); }}return res
  }
Copy the code

Let’s look at the result of the merge with a specific example.

var Parent = Vue.extend({
  mounted() {
    console.log('parent')}})var Child = Parent.extend({
  mounted() {
    console.log('child')}})var vm = new Child().$mount('#app');

// Output result:
parent
child
Copy the code

To summarize: For lifecycle hook options, options that are the same as those of the subclass and the parent class are merged into an array, so that when the subclass hook function is executed, the parent class hook option is executed, and the parent takes precedence over the child.

1.9 Watch option merge

When developing with Vue, we sometimes need custom listeners to respond to changes in data, and Watch tends to be efficient when asynchronous or expensive operations need to be performed when data changes. For the watch option merge processing, it is similar to the lifecycle hook. As long as the parent option has the same observation field, it merges with the child option into an array, and executes the parent option’s listening code while monitoring the field changes. The difference between the treatment and the life hook option is that the life cycle hook option must be a function, whereas the watch option can ultimately be the object containing the option, the corresponding callback function, or the name of the method in the merged array.

strats.watch = function (parentVal,childVal,vm,key) {
    // Firefox has the Watch method on top of the Object prototype, which is compatible with this phenomenon
    // var nativeWatch = ({}).watch;
    if (parentVal === nativeWatch) { parentVal = undefined; }
    if (childVal === nativeWatch) { childVal = undefined; }
    If there are no children, the parent option is used by default
    if(! childVal) {return Object.create(parentVal || null)} {// Make sure the watch option is an object
      assertObjectType(key, childVal, vm);
    }
    // If there is no parent, use the child option
    if(! parentVal) {return childVal }
    var ret = {};
    extend(ret, parentVal);
    for (var key$1 in childVal) {
      var parent = ret[key$1];
      var child = childVal[key$1];
      // The parent's options are first converted to arrays
      if (parent && !Array.isArray(parent)) {
        parent = [parent];
      }
      ret[key$1] = parent
        ? parent.concat(child)
        : Array.isArray(child) ? child : [child];
    }
    return ret
  };
Copy the code

Here is a specific example to see the result of the merge:

var Parent = Vue.extend({
  watch: {
    'test': function() {
      console.log('parent change')}}})var Child = Parent.extend({
  watch: {
    'test': {
      handler: function() {
        console.log('child change')
      }
    }
  },
  data() {
    return {
      test: 1}}})var vm = new Child().$mount('#app');
vm.test = 2;
// Output the result
parent change
child change
Copy the code

To summarize: The watch option is merged with the parent option into an array, and the array option member can be a callback function, an option object, or a function name.

1.10 Props Methods Inject Computed Merge

Source props to the design of the methods, inject, computed boils down to a class, their allocation strategy is consistent, simple generalization is, if the parent class does not exist, it returns a subclass options, subclass parent have to exist, with subclasses option to override the superclass option.

// Other options merge policies
strats.props =
strats.methods =
strats.inject =
strats.computed = function (parentVal,childVal,vm,key) {
  if (childVal && "development"! = ='production') {
    assertObjectType(key, childVal, vm);
  }
  if(! parentVal) {return childVal } // If the parent class does not have the option, return the option of the subclass
  var ret = Object.create(null);
  extend(ret, parentVal); // 
  if (childVal) { 
    // The subclass option overrides the value of the parent option
    extend(ret, childVal); } 
  return ret
};

Copy the code

1.11 summary

At this point, 5 kinds of options merger strategy analysis to this end, the content of the review of this chapter, this section is the hands of Vue source code analysis type, so we are starting from the introduction of the Vue, first overview the Vue introduced in code phase of the operation, mainly to the method of static method and prototype attribute definitions and declarations, It’s not necessary to know the exact functionality and implementation details of each method, but I’m sure you’ve already seen some of these methods in action. Moving on to the main point of the article, new Vue is the key to using Vue properly, and the instantiation phase initializes the _init method. Option merge is the first step of initialization. Option merging merges options defined internally with those of the child and parent classes. Vue has a rich set of options merging policies, both internal and user-defined. They all follow the agreed merging policies. With a wealth of options and a strict merge strategy, Vue is more complete in guiding development. The next section examines an important concept, data proxies, that is also the basis of responsive systems.

  • An in-depth analysis of Vue source code – option merge (1)
  • An in-depth analysis of Vue source code – option merge (2)
  • In-depth analysis of Vue source code – data agents, associated child and parent components
  • In-depth analysis of Vue source code – instance mount, compile process
  • In-depth analysis of Vue source code – complete rendering process
  • In-depth analysis of Vue source code – component foundation
  • In-depth analysis of Vue source code – components advanced
  • An in-depth analysis of Vue source code – Responsive System Building (PART 1)
  • In – Depth Analysis of Vue source code – Responsive System Building (Middle)
  • An in-depth analysis of Vue source code – Responsive System Building (Part 2)
  • In-depth analysis of Vue source code – to implement diff algorithm with me!
  • In-depth analysis of Vue source code – reveal Vue event mechanism
  • In-depth analysis of Vue source code – Vue slot, you want to know all here!
  • In-depth analysis of Vue source code – Do you understand the SYNTAX of V-Model sugar?
  • In-depth analysis of Vue source – Vue dynamic component concept, you will be confused?
  • Thoroughly understand the keep-alive magic in Vue (part 1)
  • Thoroughly understand the keep-alive magic in Vue (2)