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 initialization process of Vue, and no longer afraid of the interviewer's interview question: what happened to new Vue(options)?
Find the entrance
If you want to know what new Vue(options) does, you must first find out where Vue's constructor is declared. There are two ways:
-
Find the compiled entry from the rollup configuration file, and then find the Vue constructor step by step. This method is laborious
-
By writing sample code and then breaking points, it is simple and in place in one step
Let's adopt the second method, write examples, break points and reach the goal in one step.
- Add a sample file in the / examples Directory - test HTML, add the following contents to the file:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue Source code interpretation</title> </head> <body> <div id="app"> {{ msg }} </div> <script src="../dist/vue.js"></script> <script> debugger new Vue({ el: '#app', data: { msg: 'hello vue' } }) </script> </body> </html>
- Open the console in the browser, and then open test HTML, you will enter breakpoint debugging, and then find the file where the Vue constructor is located
Click to view the dynamic diagram of the demonstration , dynamic diagram address: https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d839ea6f3e5d4adcaf1ea9a8f6ff1a70~tplv-k3u1fbpfcp-watermark.awebp
Get the Vue constructor in / SRC / core / instance / index JS file, then officially start reading the source code and read it with the target.
In the process of reading, if you encounter something you can't understand, you can write sample code, and then use the debugging function of the browser to debug step by step to cooperate with understanding. If you still can't understand, make a note and continue to look back. Maybe you see other places and suddenly understand what this place is doing, or look back and you will understand the source code, Be sure to read more. If you want to master it, it's not enough to read it again and again
Interpretation of source code -- Vue initialization process
Vue
/src/core/instance/index.js
import { initMixin } from './init' // Vue constructor function Vue (options) { // Call Vue prototype._ Init method, which is defined in initMixin this._init(options) } // Define Vue prototype._ Init method initMixin(Vue) export default Vue
Vue.prototype._init
/src/core/instance/init.js
/** * Define Vue prototype._ Init method * @param {*} Vue Vue Constructor */ export function initMixin (Vue: Class<Component>) { // Responsible for Vue initialization process Vue.prototype._init = function (options?: Object) { // vue instance const vm: Component = this // Each vue instance has one_ uid, and it increases in turn vm._uid = uid++ // a flag to avoid this being observed vm._isVue = true // Processing component configuration items if (options && options._isComponent) { /** * When initializing each sub component, we only do some performance optimization here * Put some deep-seated attributes on the component configuration object into VM$ Options option to improve the efficiency of code execution */ initInternalComponent(vm, options) } else { /** * When initializing the root component, go here to merge the global configuration of Vue into the local configuration of the root component, such as Vue The global components registered by component are merged into the components option of the root instance * As for the option combination of each sub component, it occurs in two places: * 1,Vue.component The global components registered by the method are merged when registering * 2,{ components: { xx } } When executing the render function generated by the compiler, the local components registered in the mode are combined with options, including the configuration of components in the root component */ vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // Set the proxy to proxy the properties on the vm instance to the vm_ renderProxy initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // Initialize component instance relationship attributes, such as $parent, $children, $root, $refs, etc initLifecycle(vm) /** * We need to pay attention to this point when initializing custom events, so the listener of events registered on < comp @ Click = "handleclick" / > is not the parent component, * It is the sub component itself, that is, the event dispatcher and listener are the sub component itself, which has nothing to do with the parent component */ initEvents(vm) // Parse the slot information of the component to get the VM$ Slot, process the rendering function to get VM$ CreateElement method, i.e. h function initRender(vm) // Call the beforeCreate hook function callHook(vm, 'beforeCreate') // Initialize the inject configuration item of the component to get the configuration object in the form of result[key] = val, then process the result data in a responsive manner and proxy each key to the vm instance initInjections(vm) // resolve injections before data/props // The focus of data response is to deal with props, methods, data, calculated and watch initState(vm) // Resolve the provide object on the component configuration item and mount it to VM_ On the provided property initProvide(vm) // resolve provide after data/props // Call the created hook function callHook(vm, 'created') // If the El option is found on the configuration item, the $mount method will be called automatically, that is, with the El option, you don't need to call $mount manually. On the contrary, if there is no el, you must call $mount manually if (vm.$options.el) { // Call the $mount method to enter the mount phase vm.$mount(vm.$options.el) } } }
resolveConstructorOptions
/src/core/instance/init.js
/** * Resolve the configuration object options from the component constructor and merge the base class options * @param {*} Ctor * @returns */ export function resolveConstructorOptions (Ctor: Class<Component>) { // Configuration item let options = Ctor.options if (Ctor.super) { // If there is a base class, recursively resolve the options of the base class constructor const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // Description the base class constructor option has changed and needs to be reset Ctor.superOptions = superOptions // Check ctor Are there any later modified / additional options on options (4976) const modifiedOptions = resolveModifiedOptions(Ctor) // If there are modified or added options, merge the two options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } // Select merge and assign the merge result to ctor options options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options }
resolveModifiedOptions
/src/core/instance/init.js
/** * Resolve the options that are subsequently modified or added in the constructor options */ function resolveModifiedOptions (Ctor: Class<Component>): ?Object { let modified // Constructor options const latest = Ctor.options // Sealed constructor option, backup const sealed = Ctor.sealedOptions // Compare the two options and record the inconsistent options for (const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} modified[key] = latest[key] } } return modified }
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) { // strats = Object.create(null) 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 }
initInjections
/src/core/instance/inject.js
/** * Initialize inject configuration item * 1,Get result[key] = val * 2,Process the result data in a responsive manner, and proxy each key to the vm instance */ export function initInjections (vm: Component) { // Parse the inject configuration item, then find the Val corresponding to each key in the configuration item from the configuration of the ancestral component, and finally get the result of result[key] = val const result = resolveInject(vm.$options.inject, vm) // The result is processed in a data responsive manner, which can also be used to proxy each key in the inject configuration to the vm instance. // It is not recommended to change these data in the sub component, because once the provide r injected in the ancestral component changes, the changes you make in the component will be overwritten if (result) { toggleObserving(false) Object.keys(result).forEach(key => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { defineReactive(vm, key, result[key], () => { warn( `Avoid mutating an injected value directly since the changes will be ` + `overwritten whenever the provided component re-renders. ` + `injection being mutated: "${key}"`, vm ) }) } else { defineReactive(vm, key, result[key]) } }) toggleObserving(true) } }
resolveInject
/src/core/instance/inject.js
/** * Resolve the inject configuration item and find the value corresponding to the key from the provide configuration of the ancestral component. Otherwise, use the default value, and finally get the result[key] = val * inject The object must be the following structure, because the component configuration object is standardized when merging options * @param {*} inject = { * key: { * from: provideKey, * default: xx * } * } */ export function resolveInject (inject: any, vm: Component): ?Object { if (inject) { // inject is :any because flow is not smart enough to figure out cached const result = Object.create(null) // All key s of inject configuration item const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject) // Traversal key for (let i = 0; i < keys.length; i++) { const key = keys[i] // Skip__ ob__ object // #6574 in case the inject object is observed... if (key === '__ob__') continue // Get the corresponding key in the provide const provideKey = inject[key].from let source = vm // Traverse all ancestral components until the root component, find the value of the corresponding key in provide, and finally get result[key] = provide[provideKey] while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { result[key] = source._provided[provideKey] break } source = source.$parent } // If the previous loop is not found, use inject [key] Default. If the default value is not set, an error will be thrown if (!source) { if ('default' in inject[key]) { const provideDefault = inject[key].default result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault } else if (process.env.NODE_ENV !== 'production') { warn(`Injection "${key}" not found`, vm) } } } return result } }
initProvide
/src/core/instance/inject.js
/** * Resolve the provide object on the component configuration item and mount it to VM_ On the provided property */ export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } }
summary
What does Vue's initialization process (new Vue(options)) do?
-
Processing component configuration items
-
When initializing the root component, the options are merged to merge the global configuration into the local configuration of the root component
-
When initializing each sub component, some performance optimizations are made, and some deep-seated attributes on the component configuration object are put into VM$ Options option to improve the efficiency of code execution
-
-
Initialize the relationship attributes of component instances, such as $parent, $children, $root, $refs, etc
-
Handling custom events
-
Call the beforeCreate hook function
-
Initialize the inject configuration item of the component to obtain the configuration object in the form of ret[key] = val, then perform shallow response processing on the configuration object (only the first layer data of the object is processed), and proxy each key to the vm instance
-
Data response, processing props, methods, data, computed, watch and other options
-
Resolve the provide object on the component configuration item and mount it to VM_ On the provided property
-
Call the created hook function
-
If the el option is found on the configuration item, the $mount method will be called automatically, that is, with the el option, there is no need to call the $mount method manually. On the contrary, if the el option is not provided, the $mount method must be called
-
Then enter the mount phase
link
- Supporting video, focusing on 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.