• Learning materials: Vue source code analysis, Vue source code

1. The introduction of Vue

1.1 the constructor

  • The == essence of Vue is a constructor ==
    • Follow the UMD specification
  • Function Vue does two things internally
    • Direct calls are not allowed. Only new instances can be created
    • Call the this._init(options) method to initialize
function Vue (options) {
    Vue() cannot be called directly, only new can be used to create instance
    if(! (this instanceof Vue)
    ) {
      warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
  }
Copy the code

1.2 Defining attribute methods on prototypes (Introduction)

initMixin

  • Internally add the _init method to the Vue prototype, which defines the initialization operations within the Vue during initialization within _init

stateMixin

  • Do data-related attribute methods internally, such as proxy data access methods such as this.$data

eventsMixin

  • Define event-related methods, such as vm.$on
  • A total of four event methods are implemented
    • $on, bind event and handler
    • $off, unbind the function
    • $once, one-time event binding
    • $emit, which is equivalent to manually triggering an event

lifecycleMixin

  • Define lifecycle related methods
  • Three methods are implemented
    • _update update
    • $forceUpdate Forces the update
    • $destroy destroy

rednerMixin

  • Define methods related to rendering
  • $nextTick delays the callback to the next cycle
  • And _render rendering

1.3 Definition of static properties and methods

  • Rich global API methods are defined in initGlobalAPI
    • Vue. Read the config
    • Configuration of utility classes (such as WARN) (not exposed to public use)
    • vue.set vue.del vue.nextTick
    • rightVue.components,Vue.directive,Vue.filterThese are the default resource options constructor options
    • Vue.use Vue.mixin Vue.extend

2. Default options for the constructor

  • Provides built-in KeepAlive, Transition, and transitionGroup components for components
  • Provides built-in directives V-model, V-show for directives
  • Filter configuration item (no default value)
  • The constructor itself _base

Extend method

  • Function: Object merge
  • Parameters: object 1 to, object 2 _FROM
  • Action: Merge _from into TO, overwriting the to attribute if the same attribute is desired
  • Return value: to
// Merge the _from object into the to object, overwriting the attributes of the to object with the same attributes
function extend (to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to
}

Copy the code
  • The above built-in components constructed in Vue, directives are incorporated into components and directives by the EXTEND method

Summary: The default configuration of the Vue constructor

Vue.options = {
  components: {
    KeepAlive: {}
    Transition: {}
    TransitionGroup: {}},directives: {
    model: {inserted: ƒ.componentUpdated: ƒ}
    show: {bind: ƒ.update: ƒ.unbind: ƒ}},filters: {},
  _base
}
Copy the code

3. Check options before merging

  • The first step in initialization is option merge, which combines the default configuration with the user-entered configuration and hangs it on $options (accessible from vue instances).

  • The first step in merging is verification. Because the user’s input is not controllable, set strict specification limits and check that the user complies with those specifications before merging

  • The inspection focuses on props, Inject, and directives

    // Check and standardize the props,inject,directives
    normalizeProps(child, vm);
    normalizeInject(child, vm);
    normalizeDirectives(child);
Copy the code
  • There are other options to check

Components specification check

  • Wrapped in the method checkComponents, called in the merge option method mergeOptions

  • Function: Iterates over the property value of each Components object to verify it

  for (var key in options.components) {
    validateComponentName(key);
  }
Copy the code

ValidateComponentName method

  • Function: Verify label validity
    • Does the naming comply with rules, such as not starting with a number
    • You cannot use vUE prefabricated component names or HTML reserved words

Props specification verification

The form of the props

  • Props is written in two ways

    • Array format: props:[‘a’,’b’]
    • Object syntax :{a:{type:’String’,default:’},b:{… }}
  • The reality is eventually internally unified into object form

Props verification: normalizeProps

Some of the tools used

  • Camelize: The unified naming form is the hump form, that is, a-B is changed to aB
  • IsPlainObject: Determines whether it is a simple Object, that is, created using {} or new Object

Check the process

  1. Array: Array. IsArray: Array. IsPlainObject: object
  2. Is an array
    1. Iterate backwards, taking each array element and checking if it is of type string. The array pass must be string
    2. Is a string, calls the camelize unified form, and then overwrites it to an object form, which is stored in res
    3. Not string, error warning
  3. Is an object
    1. Traverse each object in props
    2. Normalize its key
    3. Normalize Type
    4. Save to res
  4. Not array or object: Error warning

Inject specification check

Dojo.provide/inject profile

  • Purpose: Used by a parent to pass data to a descendant component
  • Features:
    • Descendants are not limited to the offspring, and all descendants can use the data provided by them. Future generations don’t need to know where the data came from, right
    • Non-reactive: that is, subsequent changes to these values are not updated in real time in future generations

Change to reactive

Deliberately unresponsive, but still responsive if passed a listener

  • Method 1
    • To convert a value to a function, use the arrow function to ensure that this points to it, and pass the value in return using this.val
    • When used in the form of a call function
    • ** Disadvantages: Frequent function calls. ** For example, in the following code, color is used in two places, and this function is called twice
Provide (){return {color:()=>{return this.color}}} // use <template> <div "Style =" {' color: color ()} "> handed down to the color of the {{color ()}} < / div > < / template >Copy the code
  • Method 2
    • Pass Vue instances instead
    • What is actually obtained in the descendant is the instance object, which is obtained in the form of.xxx
    • This is the solution used by many UI component libraries
    • A further step is to set provide directly on root
{return {color :this}} // use <template> <div :style="{'color':color.color}"> to pass down the color {{color.color}}</div> </template>Copy the code

The chain to find

  • Provide, like the prototype chain, looks up and stops when it finds it
  • The grandparent provides color, the father provides color, and the descendant component only finds the color provided by the father

A memory leak

Child component diagrams can be kept in memory using methods provided by provide, resulting in memory usage and page stalling

Inject Verification: normalizeInject

  • Inject and props have object and array forms, and the basic writing specifications are the same. Therefore, the verification process is the same. See props above

Validation of directive specification

Directive introduction

  • Function: Custom instruction
  • Install, Update, componentUpdated, unbind
    • The hook function is invoked at the corresponding time node to call the function method bound to it

usage

Definitions can be divided into two versions, the full version and the abbreviated version

  • Full version: Manually specify when methods are fired
directives: {
  focus: {
    // The definition of a directive
    inserted: function (el) {
      el.focus()
    }
  }
}
Copy the code
  • Short version: Methods are automatically bound to bind and update
  directives: {
    'focus': function(el, binding) {
        el.style.backgroundColor = binding.value
    }
  }
Copy the code

Directives Verification: normalizeDirectives

  • Flow: Iterates through objects, changing the abbreviated version to object form

4. Subclass constructor

  • The subclass constructor is the extend of Vue
  • Creates a subclass of a Vue instance
  • It is used to generate component classes when global components are mounted and the Components property is set. New instantiates the mount when the DOM is generated

Merging rules

  • Again, subclasses override the parent class
  • If missing, use the parent class

The realization of the extend

Vue.extend = function (extendOptions) {
  extendOptions = extendOptions || {};
  var Super = this;

  var name = extendOptions.name || Super.options.name;
  if (name) {
    validateComponentName(name); // Verify that the subclass name complies with the specification
  }

  // Create a subclass constructor
  var Sub = function VueComponent (options) {
    this._init(options);
  };
  Sub.prototype = Object.create(Super.prototype); // Subclasses inherit from the parent class
  Sub.prototype.constructor = Sub;
  Sub.cid = cid++;
  // Subclass and superclass constructor configuration options are merged
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  );

  return Sub // Returns the subclass constructor
};

Copy the code

5. Overview of merger strategies

  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.
  • After completing the rule validation described above, merge the subsequent logic of the mergeOptions method

Implementation method

  function mergeField (key) {
    // If there is a custom option policy, use the custom option policy, otherwise use the default policy, each policy has a function.
    var strat = strats[key] || defaultStrat; 
    options[key] = strat(parent[key], child[key], vm, key);
  }
Copy the code

call

// Parent and child are arguments to the mergeOptions method. Don't forget that they are in mergeOptions
var options = {};
  var key;
// The default policy is used if no custom option is selected
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
Copy the code

Default policy defaultStrat

  • If the child exists, we use the child; if the child does not exist, we use the father
// 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

6. Concrete merge I: Merge of general options

(1) el merger

  • El is used to mount vUE instances onto existing DOM, so it cannot be set on subclasses and subcomponents of ==

  • Merge strategy

    • Determine that it is not a subclass or subcomponent
    • Merge using the default policy
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

(2) data

  • Vue’s data merge can be viewed in three layers
  • The merge method of data takes three parameters
    • ParentVal – parent class value
    • ChildVal – subclass value
    • The vm instance –

X1 Basic judgment

  • It is well known that data must exist within a component in the form of a function return

  • So the first step in data merging is to determine whether the input is a Vue instance, based on whether the VM exists

    • If no, determine whether it is a functional form
      • If yes, perform the merge
      • If no, warning, return the parent value
    • If yes, merge directly
// Strats merge policy data merge policy
strats.data = function (parentVal, childVal, vm) {
  // vm indicates whether the instance is created for Vue. If vm does not exist, it indicates that the relationship is child and parent
  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); // is the third argument to the function that needs to be passed to the vue instance
};

Copy the code

x2 mergeDataOrFn

  • 01- Three parameters

    • parentVal
    • childVal
    • vm
  • 02- Flow: Determine if it is an instance, again by whether the VM of the parameter is passed in

    • If the parent class is not an instance, the parent class exists

      • If the subclass does not exist, return’s parent class parentVal

      • If the parent class does not exist, return subclass childVal

      • If both parent and child exist, return a function mergedDataFn. The function takes data of the parent class. Check whether parentVal and childVal are function forms. If yes, call function to get return data. Otherwise, it is data, passed directly.)

      • // The mergeData implementation is covered later, but it is only necessary to know that it is used for merging
        return mergeData(
                typeof childVal === 'function' ? childVal.call(this.this) : childVal,
                typeof parentVal === 'function' ? parentVal.call(this.this) : parentVal
              )
        Copy the code
    • Is the Vue instance

      • As above, the actual data is obtained by judging the form of data passed in by the instance
      • Gets the default data option for the VM constructor
      • If the instance does not pass data, return the default data
      • If the instance passes datareturn mergeData(instanceData,defaultData)
        • The implementation of mergeData is covered later
  • 03 – pay attention! The above process does not take place directly in mergeDataOrFn

    • There is only one thing you do in this function: determine whether the passed in is an instance and return a different function closure depending on the situation.
      • Not,return mergedDataFn
      • Is thereturn mergedInstanceDataFn
    • The previous implementation in 02 is in these two functions, and return refers to the return of these functions rather than the return of mergeDataOrFn
  • 04- Structure Schematic

function mergeDataOrFn (parentVal,childVal,vm){
	if(! vm){// Parent class
		if() {// Subclass data option does not exist
        	return parentVal 
    	}
        if() {// The superclass data option does not exist
			return childVal
        }
        // Both parent and child have data and need to be merged
        return function mergedDataFn(){
            returnmergeData(...) }}else{ // Is a Vue instance
		varInstanceData = Data option for vm instance configurationvarDefaultData = the defaultData option of the vm constructorif() {// The VM instance has configured data, which is merged with the default data
        	 return mergeData(...)
        }else{ // The vm instance is not configured with data and returns the default data
            return defaultData
        }
	}
}
Copy the code
  • The default policy for ps: provide is also this, while inject is discussed later

x3 mergeData

  • 01- Function: Merge data. MergeData is where the actual merge takes place

  • Parameters.

    • To: subclass data (instance data)
    • From: parent data (default data)
  • 03- Flow: Take the key from the parent data class and iterate through the parent data options

    • Adds a value to a subclass that is present in the parent class but not in the subclass
    if(! hasOwn(to, key)) {// 
          set(to, key, fromVal); 
        }
    Copy the code
    • To determine whether the parent class is a complex data type, it is necessary to recursively call mergeData to achieve deep copy
    • The final subclass data (parameter to) is the result of the merge,return to
  • 04 – supplement

    • The method to get the key
    var keys = hasSymbol
          ? Reflect.ownKeys(from)
          : Object.keys(from);
    Copy the code
  • Summary: mergeData’s strategy is to integrate a parent class into a subclass, using children if both parents have them, and using parents if no children have them

7. Merge 2: Merge built-in resource options

  • 01- Resource options and default merge policy
// 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
  • 02 – mergeAssets implementation
    • Function: Merge resource options
    • process
      • Creates an empty object modeled after the parent resource option
      • If the subclass’s resource option exists, merge it into the empty object and return the result (check that the resource option is an object before merging).
      • If a subclass has no resource option, return the empty object
// Resource options customize merge policies
// Parameters: 1 parent resource options, 2 subclass resource options, 3VM instance, 4 Name of value to be merged (e.g. Components, Directives, etc.)
function mergeAssets (parentVal,childVal,vm,key) {
  // The first argument to create is the prototype of the object to be created
  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
  • Example merge result: As you can see below, subclasses must go through the stereotype chain to find resources on their parent class
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

8. Concrete merge 3: Lifecycle hook function merge

  • 01- Lifecycle hooks and default merge policy
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
  • 02 – mergeHook implementation

    • Function: Merges hook function options of parent classes

    • Parameters: parentVal, childVal

    • Note that the hook function == is sequential, and the parent class should be called before the subclass. So hook functions should be stored as arrays and called sequentially

    • Flow: Merge by judging the parent and child options

      • If the parent and child have both, concat is used to merge the array and return the merge result
      • If the parent has no children, the child is guaranteed to be an array and the child is returned
      • If the parent has no children, return the parent array
      var res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal
      Copy the code
      • The result is not returned directly, but is first assigned to the variable res. The res is de-duplicated and then returned
      return res
            ? dedupeHooks(res)
            : res
      Copy the code
  • **03- dedupeHooks **

    • Function: To remove the hook function array, avoid multiple components of the hook function interaction
    • Parameter: array of hooks functions
    • Flow: It’s a simple array de-duplication process, assigning the result to a new array res,return res
  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
  • 04- Summary: The result of the merge is an array containing the parent and subclass hook functions. The parent class’s hook function is executed before the child class’s hook function is executed

9. Specific merger 4: Watch option merger

  • 01- Merge strategy: The watch merge is similar to the hook function in that it needs to execute the parent class first and then the subclass, so it is also stored as an array. The difference is that the hook function result must be a function. The result of the Watch option is allowed to be an object, function, or method name

  • 02 – process

    • Compatibility with Object Watch on Firefox (omitted)
    • If the parent has no child, return is an empty object modeled after the parent
    • Make sure watch is an object
    • If a child has a father, return a child
    • Father and son have
      • Declare the empty object RET and merge ret with the parent
      • Loop through the child’s property name key, and get the value of that property for the parent and child respectively
      • Gets the parent property as an array
      • If the father exists, the father concat the child; If not, the child is converted to an array. The result is assigned to the RET object as the attribute value for the key
      • return ret
  • 03- Example: Execute parent watch first, then child watch

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
  • 04- Summary: The end result is to merge parent and child options into an array. Array values can be objects, functions, or function names

10. Combination 5: Combination of options of the same combination policies, such as props and methods

  • Properties: props, methods, Inject, computed

  • Strategy:

    • There is no son with a father
    • There exists a son
    • The child of the same selection takes precedence, and the child overrides the parent
 // All four are the same method
  strats.props =
  strats.methods =
  strats.inject =
  strats.computed = function (parentVal, childVal, vm, key) {
    if (childVal && "development"! = ='production') {
      assertObjectType(key, childVal, vm);
    }
    // If the parent does not exist, use the child
    if(! parentVal) {return childVal }
    var ret = Object.create(null);
    extend(ret, parentVal);
    // The child overwrites the parent
    if (childVal) { extend(ret, childVal); }
    return ret
  };
Copy the code