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.
Options. El, the options. PropsData
Use the default merge strategy;The options. The hook, the options. Watch
Life 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.Options.com ponents, Options. caching, Options. filters
The 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;Options. Props, options. Methods, options.computed
A 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.options.data
If 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:
- 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
- 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:
- If no arguments are provided, all event listeners are removed;
- 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
$off
Method to remove; - 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;
- If only events are provided, remove all listeners for that event;
- 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 functions
cbs
If thecbs
One of the terms in andfn
Of the same or a particular termfn
Properties andfn
Same, 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!!