Vue Instance Initialization Option mix Initial Merge & Option Normalization Check
(This 2005 word will take about 25-35 minutes)
1.Vue Instance Initialization Option mix initial merge (detailed merge will be mentioned in subsequent articles, if you need to view merge policy, please bypass the article-)
The merge is then mounted into the instance's $options property, which can be accessed in the instance via this.$options
var uid$3 = 0; function initMixin (Vue) { Vue.prototype._init = function (options) { var vm = this; // a uid records how many vue objects are instantiated vm._uid = uid$3++; ... // a flag to avoid this being observed vm._isVue = true; // merge options - Option merge, assigning merge values to the $options property of the instance if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // Internal component options require special handling. initInternalComponent(vm, options); } else { // The Vue instance is not a component and will execute the mergeOptions method // Merge the two option objects into a new object. // Core utilities for instantiation and inheritance. vm.$options = mergeOptions( // Returns the configuration item for the Vue constructor itself resolveConstructorOptions(vm.constructor), options || {}, vm ); } ... }; }
By studying the resolveConstructorOptions function, we find that:
function resolveConstructorOptions (Ctor) { var options = Ctor.options; // There is a super attribute that describes the subclasses that Vue.extend() builds (described below) if (Ctor.super) { var superOptions = resolveConstructorOptions(Ctor.super); // options on the Vue constructor, such as directives,filters,... var cachedSuperOptions = Ctor.superOptions; if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions; // check if there are any late-modified/attached options (#4976) var modifiedOptions = resolveModifiedOptions(Ctor); // update base extend options if (modifiedOptions) { // Object Merge extend(Ctor.extendOptions, modifiedOptions); } // Merge the two option objects into a new object. options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions); if (options.name) { options.components[options.name] = Ctor; } } } return options }
Note: There is a super attribute that indicates the subclass that Vue.extend() builds, which adds a super attribute to the Ctor pointing to its parent constructor
Vue.extend = function (extendOptions: Object): Function { ... Sub['super'] = Super ... }
Ctor is the basic Vue constructor, which prints superOtions with the following structure:
{ "components": {}, "directives": {}, "filters": {}, "beforeCreate": [], "beforeMount": [], "beforeDestroy": [], "destroyed": [] }
Let's look at the mergeOptions section:
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */ function mergeOptions ( parent, child, vm ) { { // components check checkComponents(child); } if (typeof child === 'function') { child = child.options; } // Checksum and normalization of normalized props,inject,directives normalizeProps(child, vm); normalizeInject(child, vm); normalizeDirectives(child); // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. if (!child._base) { // When the incoming options have mixin/extends properties, call the mergeOptions method again to merge the contents of mixins and extends onto the instance's constructor options (that is, parent options), as in the case of Example 1 below if (child.extends) { parent = mergeOptions(parent, child.extends, vm); } if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } } } // Core Section var options = {}; var key; for (key in parent) { mergeField(key); } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key); } } function mergeField (key) { // Get the option configuration specified by each selection, or default configuration if not var strat = strats[key] || defaultStrat; // Execute merge policy options[key] = strat(parent[key], child[key], vm, key); } return options }
Example 1: The incoming mounted, created hook handler, and method methods are proposed to merge with parent options.
const childComponent = Vue.component('child', { ... mixins: [myMixin], extends: myComponent ... }) const myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin') } } } const myComponent = { mounted: function () { this.yes() }, methods: { yes: function () { console.log('yes from mixin') } } }
For a more asynchronous understanding, refer to the merge flowchart description of a big man:
**It is not clear what configuration options were passed by the user in the option merge and whether they met the requirements of the merge configuration specification. Therefore, the writing rules of each option need to be strictly restricted, and in principle users are not allowed to pass out of the rule options out of the box. **So a large part of the work before merging options is to verify the options. Among the components,prop,inject,directive and others are the focus of the test.
2. Option Checks - components(checkComponents)
Code:
/** * unicode letters used for parsing html tags, component names and property paths. * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname * skipping \u10000-\uEFFFF due to it freezing up PhantomJS */ // unicode letters for resolving HTML tags, component names, and property paths. Use https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname, skip \u10000-uEFFFF because it freezes PhantomJS // ps: Here PhantomJS is a webkit-based JavaScript API. It uses QtWebKit as its core browser function and WebKit to compile, interpret and execute JavaScript code. Also known as Headless Browser var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; /** * Validate component names */ function checkComponents (options) { for (var key in options.components) { validateComponentName(key); } } function validateComponentName (name) { if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'should conform to valid custom element name in html5 specification.' ); } // Mainly to prevent custom components from using HTML built-in tags such as svg and component names defined by Vue itself such as slot, component, if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ); } }
3. Option Check - props(normalizeProps)
Quote the official Vue2.x line:
-
Type: Array<string> | Object
-
Detailed:
Pros can be arrays or objects that receive data from parent components. Pros can be simple arrays or use objects as substitutes, which allow configuration of advanced options such as type detection, custom validation, and setting defaults.
Come from:https://cn.vuejs.org/v2/api/#props
Code:
/** * Ensure all props option syntax are normalized into the * Object-based format. */ function normalizeProps (options, vm) { var props = options.props; if (!props) { return } var res = {}; var i, val, name; // Array<string> | Object if (Array.isArray(props)) { // array i = props.length; while (i--) { val = props[i]; // If an item of the array is a string if (typeof val === 'string') { // Names will be canonicalized (hump) name = camelize(val); // ResAdd attribute, type defaults to null res[name] = { type: null }; // eg: If passed in ['first-prop','second-prop'], it will be converted to: {firstProp: {type: null}, secondProp: {type: null}} } else { warn('props must be strings when using array syntax.'); } } } else if (isPlainObject(props)) { // object for (var key in props) { // val assignment val = props[key]; // Hump Name Formatting name = camelize(key); /* * Incoming props: {name: String} => {name: {type: String}} * Incoming props: {name: {type: String, required: true}} => * { name: { type: String, reuired: true } } */ res[name] = isPlainObject(val) ? val : { type: val }; } } else { // Non-array, non-object, illegal delivery warn( "Invalid value for option \"props\": expected an Array or an Object, " + "but got " + (toRawType(props)) + ".", vm ); } options.props = res; }
To sum up, with this normalization check, props Array <string> | Object is normalized by name humps and a series of other processes, and will eventually be converted into object form, Yoga YDS = =!
4. Option Check - inject (normalizeInject)
Quote the official Vue2.x line:
provide/inject dependent injection
-
Type:
- provide: Object | () => Object
- inject: Array<string> | { [key: string]: string | Symbol | Object }
-
Detailed:
The provide option should be an object or a function that returns an object. The object contains properties that can be injected into its descendants
The inject option should be
- An array of strings, or
- An object whose key is the local binding name and whose value is:
- The key (string or Symbol) to search for in the available injections, or
- An object whose:
- from property is the key (string or symbols) used to search for available injections
- default property is the value used in the case of demotion
Come from:https://cn.vuejs.org/v2/api/#provide-inject
If you don't know the use of provide/inject, you can access ithttps://cn.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5
/** * Normalize all injections into Object-based format */ function normalizeInject (options, vm) { // Cache inject objects as intermediate variables var inject = options.inject; if (!inject) { return } // Empty it and ensure that normalized and options.inject point to the same object var normalized = options.inject = {}; if (Array.isArray(inject)) { for (var i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] }; } } else if (isPlainObject(inject)) { for (var key in inject) { var val = inject[key]; normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; } } else { warn( "Invalid value for option \"inject\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm ); } }
Overall, with this normalization check, inject is eventually converted into an object similar to props
5. Option Checks - directives (normalizeDirectives)
Type: Object
Code:
/** * Normalize raw function directives into object format. */ function normalizeDirectives (options) { var dirs = options.directives; if (dirs) { for (var key in dirs) { var def$$1 = dirs[key]; if (typeof def$$1 === 'function') { dirs[key] = { bind: def$$1, update: def$$1 }; } } } }
Read through the code, Vue allows us to use custom instructions and provides the following five types of hooks
We can do this in the following ways:
{ directives: { 'demo': function (el,binding) { console.log(binding.value.color) // => "white" } } }
As a result, when directives are normalized, the behavior is assigned to bind,update hook, and callback for function writing
Thanks♪(・ω・)ノ