When learning becomes a habit, knowledge becomes common sense. Thank you for your likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.
target
Deeply understand the implementation principle of the following global API s.
- Vue.use
- Vue.mixin
- Vue.component
- Vue.filter
- Vue.directive
- Vue.extend
- Vue.set
- Vue.delete
- Vue.nextTick
Source code interpretation
From the first article in the series Interpretation of Vue source code (1) -- Preface It can be seen from the introduction to the source directory structure in that most of Vue's global APIs are implemented in the / SRC / core / global API directory. These global API source code reading entries are in / SRC / core / global API / index JS file.
entrance
/src/core/global-api/index.js
/** * Initialize Vue's many global API s, such as: * Default configuration: Vue config * Tool method: Vue util. xx * Vue.set,Vue.delete,Vue.nextTick,Vue.observable * Vue.options.components,Vue.options.directives,Vue.options.filters,Vue.options._base * Vue.use,Vue.extend,Vue.mixin,Vue.component,Vue.directive,Vue.filter * */ export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} // Many default configuration items for Vue configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // Vue.config Object.defineProperty(Vue, 'config', configDef) /** * Expose some tools and methods. Don't use these tools and methods easily. You know these tools and methods well and know the risks of using them */ Vue.util = { // Warning log warn, // Similar options merge extend, // Merge options mergeOptions, // Set responsive defineReactive } // Vue.set / delete / nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick // Responsive method Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // Vue.options.compoents/directives/filter Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // Mount the Vue constructor to Vue options._ Base Vue.options._base = Vue // In Vue options. Add built-in components to components, such as keep alive extend(Vue.options.components, builtInComponents) // Vue.use initUse(Vue) // Vue.mixin initMixin(Vue) // Vue.extend initExtend(Vue) // Vue.component/directive/filter initAssetRegisters(Vue) }
Vue.use
/src/core/global-api/use.js
/** * Define Vue Use, responsible for installing plug-ins for Vue, did the following two things: * 1,Judge whether the plug-in has been installed. If it is installed, it will end directly * 2,Install the plug-in and execute the install method of the plug-in * @param {*} plugin install Method or an object containing the install method * @returns Vue example */ Vue.use = function (plugin: Function | Object) { // List of installed plug-ins const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // Judge whether the plugin has been installed to ensure that it is not installed repeatedly if (installedPlugins.indexOf(plugin) > -1) { return this } // Put the Vue constructor in the first parameter position and pass these parameters to the install method const args = toArray(arguments, 1) args.unshift(this) if (typeof plugin.install === 'function') { // If the plugin is an object, execute its install method to install the plugin plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // Execute the direct plugin method to install the plug-in plugin.apply(null, args) } // Add a newly installed plug-in to the plug-in list installedPlugins.push(plugin) return this }
Vue.mixin
/src/core/global-api/mixin.js
/** * Define Vue Mixin is responsible for global blending options, affecting all Vue instances created later. These instances will merge global blending options * @param {*} mixin Vue Configuration object * @returns Return Vue instance */ Vue.mixin = function (mixin: Object) { // Merge mixin objects on Vue's default configuration items this.options = mergeOptions(this.options, mixin) return this }
mergeOptions
src/core/util/options.js
/** * Merge two options. When the same configuration item appears, the child option will overwrite the configuration of the parent option */ export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } if (typeof child === 'function') { child = child.options } // Standardize props, inject and directive options to facilitate the processing of subsequent programs normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // Process the extensions and mixins on the original child object, execute mergeOptions respectively, and merge these inherited options into the parent // The objects processed by mergeOptions will contain_ base attribute if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key // Traverse parent options for (key in parent) { mergeField(key) } // Traverse the sub options. If the parent option does not have the configuration, merge it. Otherwise, skip it. Because the parent and child have the same attribute, it has been processed when processing the parent option above, and the value of the sub option is used for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } // For merge options, childVal takes precedence over parentVal function mergeField (key) { // strat is the merge policy function. If the key conflicts, childVal will overwrite parentVal const strat = strats[key] || defaultStrat // If childVal exists, childVal is preferred; otherwise, parentVal is used options[key] = strat(parent[key], child[key], vm, key) } return options }
Vue.component,Vue.filter,Vue.directive
/src/core/global-api/assets.js
The implementation of these three API s is quite special, but the principle is very similar, so they are implemented together.
const ASSET_TYPES = ['component', 'directive', 'filter'] /** * Define Vue component,Vue.filter,Vue. Direct these three methods * What these three methods do is similar, that is, in this options. Store the corresponding configuration on XX * Like Vue The result of component (componame, {XX}) is this options. components. Compname = component constructor * ASSET_TYPES = ['component', 'directive', 'filter'] */ ASSET_TYPES.forEach(type => { /** * For example: Vue component(name, definition) * @param {*} id name * @param {*} definition Component constructor or configuration object * @returns Return component constructor */ Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { if (type === 'component' && isPlainObject(definition)) { // If name exists in the component configuration, use it; otherwise, use id directly definition.name = definition.name || id // extend is Vue In this case, the constructor of definition becomes new () definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // this.options.components[id] = definition // When instantiating, merge the globally registered components into the components of the configuration object of each component through mergeOptions this.options[type + 's'][id] = definition return definition } } })
Vue.extend
/src/core/global-api/extend.js
/** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. */ Vue.cid = 0 let cid = 1 /** * Extend the subclass based on Vue, which also supports further extension * Some default configurations can be passed during expansion, just as Vue also has some default configurations * If the default configuration conflicts with the base class, options will be merged */ Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const SuperId = Super.cid /** * Using the cache, if it exists, the constructor in the cache is returned directly * Under what circumstances can this cache be utilized? * If you call Vue multiple times The same configuration item (extendOptions) is used during extend, and the cache will be enabled */ const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } // Define the Sub constructor, just like the Vue constructor const Sub = function VueComponent(options) { // initialization this._init(options) } // Inherit Vue through prototype inheritance Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ // Option merge: merge Vue configuration items into your own configuration items Sub.options = mergeOptions( Super.options, extendOptions ) // Record your own base class Sub['super'] = Super // Initialize props and proxy the props configuration to sub.prototype_ Props object // Pass this. In the component_ Props mode can be accessed if (Sub.options.props) { initProps(Sub) } // Initialize computed and proxy the computed configuration to the Sub.prototype object // In the component, you can use this Computedkey access if (Sub.options.computed) { initComputed(Sub) } // Three static methods, extend, mixin and use, are defined to allow subclasses to be further constructed on the basis of Sub Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // Define three static methods: component, filter and directive ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // The principle of recursive components. If the component sets the name attribute, it will register itself in its components option if (name) { Sub.options.components[name] = Sub } // Keep references to base class options when extending. // Later, when instantiating, we can check whether the option of Super has an update Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache cachedCtors[SuperId] = Sub return Sub } function initProps (Comp) { const props = Comp.options.props for (const key in props) { proxy(Comp.prototype, `_props`, key) } } function initComputed (Comp) { const computed = Comp.options.computed for (const key in computed) { defineComputed(Comp.prototype, key, computed[key]) } }
Vue.set
/src/core/global-api/index.js
Vue.set = set
set
/src/core/observer/index.js
/** * Via Vue Set or this$ The set method sets the value val for the specified key of the target * If the target is an object and the key does not exist, set the response for the new key, and then execute the dependency notification */ export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // Update the elements of the array with the specified subscript, Vue Set (array, IDX, Val), and realize the responsive update through the splice method if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } // Update the existing properties of the object, Vue Set (obj, key, Val) and execute the update if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // You cannot add dynamic responsive attributes to Vue instances or $data, which is one of the uses of vmCount, // this.$data ob Vmcount = 1, indicating the VM of the root component and other sub components Vmcount is 0 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // target is not a responsive object. The new attribute will be set, but it will not be processed in a responsive manner if (!ob) { target[key] = val return val } // Define a new attribute for the object, set the response through the defineReactive method, and trigger the dependency update defineReactive(ob.value, key, val) ob.dep.notify() return val }
Vue.delete
/src/core/global-api/index.js
Vue.delete = del
del
/src/core/observer/index.js
/** * Via Vue Delete or VM$ Delete deletes the specified key of the target object * The array is implemented through the splice method, and the object deletes the specified key through the delete operator and executes the dependency notification */ export function del (target: Array<any> | Object, key: any) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } // If target is an array, delete the element of the specified subscript through the splice method if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ // Avoid deleting the properties of Vue instances or the data of $data if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } // If the attribute does not exist, end directly if (!hasOwn(target, key)) { return } // Delete the properties of an object through the delete operator delete target[key] if (!ob) { return } // Execute dependency notification ob.dep.notify() }
Vue.nextTick
/src/core/global-api/index.js
Vue.nextTick = nextTick
nextTick
/src/core/util/next-tick.js
For more detailed analysis of the nextTick method, you can see the previous article Interpretation of Vue source code (4) -- asynchronous update.
const callbacks = [] /** * Accomplish two things: * 1,Wrap the flushSchedulerQueue function with try catch and put it into the callbacks array * 2,If pending is false, it means that there is no flushCallbacks function in the browser's task queue * If pending is true, it indicates that the flushCallbacks function has been put into the browser's task queue, * When the flushCallbacks function is to be executed, pending will be set to false again, indicating that the next flushCallbacks function can enter * The browser's task queue * pending Function: ensure that there is only one flushCallbacks function in the browser's task queue at the same time * @param {*} cb Receive a callback function = > flushschedulerqueue * @param {*} ctx context * @returns */ export function nextTick (cb?: Function, ctx?: Object) { let _resolve // Use the callbacks array to store the wrapped cb function callbacks.push(() => { if (cb) { // Wrap the callback function with try catch to facilitate error capture try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true // Execute timerFunc and put the flushCallbacks function in the browser's task queue (micro task queue is preferred) timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
summary
Interviewer: Vue What did use (plugin) do?
Answer:
To install the plugin plug-in is to execute the install method provided by the plug-in.
- First, judge whether the plug-in has been installed
- If not, execute the install method provided by the plug-in to install the plug-in. The plug-in decides what to do
Interviewer: Vue What did mixin (options) do?
Answer:
Responsible for merging options configuration on Vue's global configuration. Then, when each component generates vnode, the global configuration will be merged into the configuration of the component itself.
- Standardize the format of props, inject, and directive options on the options object
- Handle extensions and mixins on options and merge them into the global configuration respectively
- Then, the options configuration and the global configuration are merged. When the options conflict, the options configuration will overwrite the global configuration
Interviewer: Vue What did component (compname, COMP) do?
Answer:
Responsible for registering global components. In fact, it is to register the component configuration on the components option of the global configuration (options.components), and then each sub component will merge the global components option into the local components configuration item when generating vnode.
- If the second parameter is empty, it means to get the component constructor of compName
- If Comp is a component configuration object, use Vue The extend method obtains the component constructor. Otherwise, proceed to the next step directly
- Set the component information on the global configuration, this options. components. compName = CompConstructor
Interviewer: Vue What did directive ('My directive ', {XX}) do?
Answer:
Register the my directive globally, and then each subcomponent will merge the global directives option into the local directives option when generating vnode. The principle is the same as Vue Component method:
- If the second parameter is null, the configuration object of the specified instruction is obtained
- If it is not empty, if the second parameter is a function, the configuration object {bind: the second parameter, update: the second parameter} will be generated
- Then set the instruction configuration object to the global configuration, this options. directives['my-directive'] = {xx}
Interviewer: Vue What does filter ('My filter ', function (VAL) {XX}) do?
Answer:
Be responsible for registering the filter my filter globally, and then each sub component will merge the global filters option into the local filters option when generating vnode. The principle is:
- If the second parameter is not provided, get the callback function of my filter filter
- If the second parameter is provided, set this options. filters['my-filter'] = function(val) {xx}.
Interviewer: Vue What does extend (options) do?
Answer:
Vue.extend creates a subclass based on Vue, and the parameter options will be used as the default global configuration of the subclass, just like the default global configuration of Vue. So through Vue Extend extends a subclass. One of its great uses is to build in some public configurations for subclasses of subclasses.
- Define a subclass constructor. Like Vue, it is also called_ init(options)
- Merge the Vue configuration and options. If the options conflict, the options will overwrite the Vue configuration items
- Define a global API for subclasses, with the value Vue, such as sub.extend = super Extend, so that the subclass can also extend other subclasses
- Return subclass Sub
Interviewer: Vue What did set (target, key, Val) do
Answer:
Because Vue cannot detect ordinary new properties (such as this.myObject.newProperty = 'hi'), Vue Set is to add a property to the responsive object, which can ensure that the new property is also responsive and trigger the view update.
- Update the element of the array with the specified subscript: Vue Set (array, IDX, Val). The internal response update is realized through the splice method
- Update the existing properties of the object: Vue Set (obj, key, Val), update directly = > obj [key] = Val
- You cannot dynamically add root level responsive data to Vue instances or $data
- Vue.set(obj, key, val). If obj is not a responsive object, obj[key] = val will be executed, but no responsive processing will be performed
- Vue.set(obj, key, val). Add a new key to the responsive object obj. Set the responsive object through the defineReactive method and trigger the dependency update
Interviewer: Vue What does delete (target, key) do?
Answer:
Delete the property of the object. If the object is responsive, make sure the deletion triggers an update of the view. This method is mainly used to avoid the restriction that Vue cannot detect that the property is deleted, but you should rarely use it. Of course, you can't delete the responsive attribute at the root level.
- Vue.delete(array, idx), which deletes the element with the specified subscript. It is implemented internally through the splice method
- Delete a property on the responsive object: Vue Delete (obj, key). Internally, delete obj is executed Key, and then execute dependency update
Interviewer: Vue What did nexttick (CB) do?
Answer:
Vue. The function of nexttick (cb) method is to delay the execution of callback function cb, which is generally used for this Key = newval after changing the data, you want to get the changed DOM data immediately:
this.key = 'new val' Vue.nextTick(function() { // DOM updated })
Its internal execution process is:
- this.key = 'new val, trigger dependency notification update, and put the watcher in charge of updating into the watcher queue
- Put the function of refreshing the watcher queue into the callbacks array
- Put a function to refresh the callbacks array in the asynchronous task queue of the browser
- Vue.nextTick(cb) to jump the queue and put the cb function into the callbacks array
- The function of refreshing the callbacks array will be executed at a certain time in the future
- Then execute many functions in the callbacks array to trigger the watcher Execute run and update DOM
- Since the cb function is put into the callbacks array later, this ensures that the DOM update is completed first, and then the cb function is executed
link
- Supporting video, WeChat official account reply Get "proficient in Vue technology stack source code principle video version":
- Proficient in Vue technology stack source code principle column
- github warehouse liyongning/Vue Welcome, Star
Thank you for your likes, collections and comments. I'll see you next time.
When learning becomes a habit, knowledge becomes common sense. Thank you for your likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.