You bet with me when you knew you were going to lose, so every gambler has their excuse

preface

First of All, why is this article called All in?

Because this is the epilogue, and tutu is going to get it. Ending in a hurry?

If it were a long series, it would be followed by “Instance methods”, “global APIS”, “filters”, “instructions”, “plug-ins”, “built-in components”, etc., which would undoubtedly be quite extensive. Tutu is in a period of rapid growth, how could it be possible to spend too much energy to write these in detail, there are too many things to learn, and according to everyone’s temperament, most of them do not have the patience to read the article, so Tutu decided to turn on the rampage mode, stew these contents in a pot, well, chaotic stew.

The ultimate goal of analyzing source code is to interpret the author’s thoughts. This process takes a lot of time, energy, and multi-angle and in-depth thinking for application scenarios, but the painful process will be impressive. You make it, you make it. Rabbit rabbit may also read roughly, but it is useful. For most people, the most real situation is that learned, forgotten, very painful. Rabbit rabbit is no exception, quickly summarize a wave, because we finally remember is only a kind of thought, so I as far as possible with refined language elaboration thought.

The body of the

Merge strategyoptionMergeStrategies

OptionMergeStrategies are primarily used for mixin and vue.extend () methods to merge strategies for child and parent components if they have the same property (option).

The default merge policy for defaultStrat is: The subcomponent has a higher priority. If the child option exists, use the child option. If the child option does not exist, use the parent option.

  1. Options. El, the options. PropsDataUse the default merge strategy;
  2. The options. The hook, the options. WatchLife cycle hook function options, custom watch options, are merged into an array, the parent component first, the child component after, that is, the parent component function first.
  3. Options.com ponents, Options. caching, Options. filtersThe merge strategy is to return a new merged object whose own properties are all derived from the child component object, but delegated to the parent component object through the stereotype chain. Attributes are searched to the parent component along the prototype chain, so the child component has higher priority;
  4. Options. Props, options. Methods, options.computedA slight difference from article 3 is that when there is a property with the same name, the parent component object is overwritten directly by the child component object, without the stereotype chain delegate.
  5. options.dataIf data exists in the component as a function, the function is executed first to retrieve the returned object. The merging still maintains the principle that the child component has higher priority. The attributes of the parent component Object are merged into the child component Object. If the parent and child components both have a certain attribute and are of Object type, the recursive merging is performed and the merged child component Object is finally returned.

Custom merge strategies: We may use them when developing plug-ins.

Official website example:

Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) {
  return child + 1
}

const Profile = Vue.extend({
  _my_option: 1
})

// Profile.options._my_option = 2
Copy the code

Vue – the router example:

const strats = Vue.config.optionMergeStrategies
  // use the same hook merging strategy for route hooks
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
Copy the code

globalAPI

Vue.extend

The whole process is to first create a class Sub, then inherit the base Vue class through prototype inheritance, then add some attributes to the Sub class and merge some attributes of the parent class into the Sub class, and finally return the Sub class.

Vue.extend = function (extendOptions) {
    const Super = this
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    / /...
    return Sub
}
Copy the code

Vue.nextTick

Since setters can be fired multiple times in a row, vue uses queues internally to store multiple callback functions (stored dependencies and callbacks when nextTick is called manually) and asynchronously executes these functions on the nextTick at once to avoid dom re-rendering multiple times. In order to achieve performance optimization.

NextTick is generally divided into two parts:

  1. Ability to detect

Internally, try using native Promise.then, MutationObserver, and setImmediate for asynchronous queues, or setTimeout(fn, 0) instead if the execution environment does not support it. Macro tasks take longer than microtasks, so microtasks are preferred when supported by the browser.

let timerFunc

if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () = > {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}Copy the code
  1. Execute the callback function queue

Two points to note:

(1) Use the concept of an asynchronous lock, that is, when receiving the first callback function, first close the lock, execute the asynchronous method. At this point, the browser is waiting for the synchronous code to finish executing before executing the asynchronous code.

const callbacks = []
let pending = false

export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  callbacks.push(() = > {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) {/ / asynchronous lock
    pending = true / / shut
    timerFunc()
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') { // Return a promise if no callback is provided
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code

(2) When the flushCallbacks function is executed, an operation is performed to back up the callback queue. The purpose is to prevent the nested inner nextTick callback function from entering the current callback queue when it should be in the next callback queue.

function flushCallbacks () {
  pending = false // Open the asynchronous lock
  const copies = callbacks.slice(0) // Back up the callback queue
  callbacks.length = 0 // Clear the callback queue of the current storage in preparation for the callback queue of the next layer of storage
  for (let i = 0; i < copies.length; i++) { // Use backup to execute
    copies[i]()
  }
}
Copy the code

Vue.set

At the end of the previous responsivity article, link listed the following question:

Many people struggle to understand why a Dep instance is created in the Observer in the first place when there is already one in defineReactive.

It was already answered there. The Observer constructor initializes an instance of Dep:

export class Observer {
  value: any;
  dep: Dep;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    / /...
  }
Copy the code

The timing of the DEP dependency collection is in the getter childob.dep.depend ():

get: function reactiveGetter () {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) {
    dep.depend()
    if (childOb) {
      childOb.dep.depend()
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
  }
  return value
}
Copy the code

The deP of this Observer instance is an object that has a purpose to serve vue. set and vue. delete:

// Vue. Set implementation key code
if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.length = Math.max(target.length, key)
  target.splice(key, 1, val)
  return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
Copy the code

For objects, vue.set converts the set properties to reactive, followed by dependency distribution updates. For an array, use the splice method to add it to the array. Notice that splice has changed the pointer of the array and added the interceptor, which still calls ob.dep.notify().

def(arrayMethods, method, function mutator (. args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify() // Post updates here
    return result
  })
Copy the code

Vue.delete

// Vue. Delete implementation key code
if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.splice(key, 1)
  return
}
delete target[key]
if(! ob) {return
}
ob.dep.notify()
Copy the code

Delete Vue. Delete Vue. Delete Vue.

Vue.directive

The essence of a custom directive is to set up a hook function to execute the directive when appropriate. That is, during the virtual DOMpatch, hook functions are executed at various times.

Vue.filter

Filters work by compiling user-written filters into the template into a _f function call string, which is then executed during the rendering function to make the filter effective.

The _f function is the alias of the resolveFilter function. Inside the resolveFilter function, the filter function is obtained from the filters attribute in $options of the current instance according to the filter ID. The filter function will be executed later when the render function is executed.

Vue.component

export const ASSET_TYPES = [
  'component'.'directive'.'filter'
]

ASSET_TYPES.forEach(type= > {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ) :Function | Object | void {
      if(! definition) {return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if(process.env.NODE_ENV ! = ='production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
Copy the code

Through the source code, we can figure out the following pseudo-code:

Vue.component = function (id, definition) {
    this.options.components[id] = this.options._base.extend(definition)
}
Copy the code

This.options._base. extend is vue.extend, so the principle of component registration is to create a Vue subclass and mount it on this.options.components. When we use this component, we fetch it from above, go through the whole process of new Vue(), and finally insert it into the parent node.

Vue.use

How it works: The install method provided by the plug-in is called internally, passing in Vue as an argument. In addition, because plug-ins are installed only once, the API should also prevent the install method from being called by the same plug-in more than once.

Vue.use = function (plugin) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) { // Avoid repeated plug-in installation
        return this
    }

    const args = toArray(arguments.1) // Get the second option object argument
    args.unshift(this) // add Vue as the first argument
    if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args) // Call the install method with two arguments [Vue, options]
    } else if (typeof plugin === 'function') {
        plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
}
Copy the code

Vue.mixin

Check out the merge Strategy section above

Instance methods

vm.$watch

Check out the three Types of Watcher-$Watch section

vm.$set

Ditto the Vue. Set

vm.$delete

Ditto the Vue. Delete

vm.$on

The $ON and $EMIT methods are the most typical publish/subscribe pattern in the design pattern. First, define an event center, subscribe to events through $ON, store the events in the event center, and then use $EMIT to trigger the subscribe events stored in the event center.

Vue.prototype.$on = function (event, fn) {
    const vm: Component = this
    if (Array.isArray(event)) { // Multiple events can be registered at once
        for (let i = 0, l = event.length; i < l; i++) {
            this.$on(event[i], fn) // Register through a loop}}else {
        (vm._events[event] || (vm._events[event] = [])).push(fn) // Add events to the event center
    }
    return vm
}
Copy the code

In the event registration event, the parent sends the custom event to the child component, which is initialized when the child component instantiates. Browser native events are handled in the parent component.

vm.$emit

Vue.prototype.$emit = function (event: string) :Component {
    const vm: Component = this
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments.1)
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)}}}return vm
  }
}
Copy the code

The CBS callback is obtained from the _events property of the current instance based on the event name passed in. Since the event center is added as a push array, this loop is also executed with input parameters.

vm.$off

Vue.prototype.$off = function (event, fn) {
    var vm = this;
    // all
    if (!arguments.length) {
      vm._events = Object.create(null);
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
        vm.$off(event[i$1], fn);
      }
      return vm
    }
    // specific event
    var cbs = vm._events[event];
    if(! cbs) {return vm
    }
    if(! fn) { vm._events[event] =null;
      return vm
    }
    // specific handler
    var cb;
    var i = cbs.length;
    while (i--) {
      cb = cbs[i];
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break}}return vm
  };
Copy the code

$off handles several cases:

  1. If no arguments are provided, all event listeners are removed;
  2. If you pass in an array of event names to remove, you need to remove multiple events at once. By iterating through the array, each event in the array is called recursively$offMethod to remove;
  3. If the name of the event to be removed is not found in the event center, it indicates that the event has never been subscribed to in the event center, so it is not possible to remove the event, directly return, exit the program;
  4. If only events are provided, remove all listeners for that event;
  5. If both the event name and the callback function are passed, only the listener for the callback is removed. Iterate through an array of callback functionscbsIf thecbsOne of the terms in andfnOf the same or a particular termfnProperties andfnSame, then it is removed from the array (compare the memory address).

vm.$once

Vue.prototype.$once = function (event, fn) {
    const vm: Component = this
    function on () {
        vm.$off(event, on)
        fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
}
Copy the code

The principle is very simple: the event is registered with $on. After the event is executed once, it is unloaded with $off.

But there’s something there. On. Fn = fn, why bother?

We register the $ON event with our custom on function, and the store in the event center will look like this:

vm._events = {
  'xxx':[on]
}
Copy the code

$off(‘ XXX ‘,fn); $off(‘ XXX ‘,fn); $off(‘ XXX ‘,fn); Have so a line judge the if (cb = = = fn | | cb, fn = = = fn), has done it to avoid mistakes.

vm.$forceUpdate

Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
        vm._watcher.update()
    }
}
Copy the code

The watcher update method is executed manually.

vm.$nextTick

Ditto the Vue. NextTick

Vm. $mount, vm. $destroy

See the previous Vue2.0 source code reading plan (vi) – life cycle

supplement

Rabbit rabbit recently very impetuous, to see vue-Router, vuex, AXIos source code, patience, read a general, briefly the principle:

vue-router

Installed as a plug-in, the VueRouter class provides static install methods that use vue. mixin with beforeCreate and destroyed hook functions. For the route initialization in beforeCreate, one of the hash, history, or Abstract modes is used based on the mode configuration item. Hash mode listens for hashChange events, and History mode listens for popState events. When it listens for changes, the changed value is used to match the routing table and switch components. It’s basically like this.

vuex

Vuex and VUE-Router perform the same installation process. Nested modules are resolved recursively, store instances are mounted to the root component, and child components are obtained from the parent component through this.$store = options.parent. Make it globally unique. We can all write a simple state management, see the official simple state management starting use, vuEX is also a strict convention, more detailed, more comprehensive consideration.

axios

It’s going to happen when we call it

1.Execute request interceptor2.Send request promise = dispatchRequest(newConfig);// The nature of dispatchRequest is as follows:
    return new Promise((resolve, reject) = > {
        // Assign attributes set by the user to config
        var request = new XMLHttpRequest();
        // Create the request
        request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
        // Configure the timeout period
        request.timeout = config.timeout;
        // Listen for the request end event
        request.onloadend = onloadend;
        function onloadend() {
            // Do something about the background return value
            resolve(response)
        }
        // Also listen onabort, onError, onTimeout events
        // Finally send the request
        request.send(requestData);
    })
    .then(res= > {
        // The response interceptor is executed
    })
3.Return to the promisereturn promise
Copy the code

The last

Keep learning and be happy!!