The article was first published on personal blog Small gray space
Option merge policy analysis
After converting the three options of projects input directive into a unified format, you can start to merge the options. Take a look at the code of option merging
// core/instance/init.js // Start merge option const options = {} let key for (key in parent) { // First, merge the options in the parent instance into the target object mergeField(key) } for (key in child) { // Traverse the child. If the attribute in the child does not exist on the parent itself, combine the attribute into the parent if (!hasOwn(parent, key)) { // mergeField(key) } } function mergeField (key) { // The combination policies of various options are defined on the starts object const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) }
When merging options, the user-defined merge policy is preferred. If the user-defined option policy does not exist, the default merge policy is used. Each key in the starts object corresponds to the merge strategy of one option
Default merge policy
The default option consolidation policy. If the subclass option exists, the subclass option is used to override the parent option
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal }
el option merge policy
The el option provides a DOM element that already exists on the page as the mount target of the Vue instance. The change option only exists on Vue instances, and the el option is not allowed on other subclass constructors
if (process.env.NODE_ENV !== 'production') { strats.el = strats.propsData = function (parent, child, vm, key) { // Only Vue instances have the el option, and other subclass constructors are not allowed to have the el option if (!vm) { warn( `option "${key}" can only be used during instance ` + 'creation with the `new` keyword.' ) } return defaultStrat(parent, child) } }
data option merge policy
strats.data = function ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { if (childVal && typeof childVal !== 'function') { // Ensure that the data type of the subclass must be a function rather than an object. Using an object to return a data type can realize service reuse, and the data between component instances will not affect each other process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal } // Call cmergeDataOrFn merge option return cmergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) } export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { // in a Vue.extend merge, both should be functions // The options passed in are those in the component, so there is no vm instance· if (!childVal) { return parentVal } if (!parentVal) { return childVal } return function mergedDataFn () { // The data option returns a function when the parent and child classes exist at the same time, and calls the real merge option of the method when the responsive system initializes later return mergeData( // Pass the returned results of the data function of the subclass instance and the parent instance to mergerData for merging typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { // The data attribute hung in the Vue instance, which can be an object return function mergedInstanceDataFn () { // instance merge const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal if (instanceData) { // Merge the options of the instance with the options in the Vue constructor return mergeData(instanceData, defaultData) } else { return defaultData } } } }
By analyzing the above code, it can be found that during Vue instance initialization, the data option does not really merge, but only returns a function, which returns the execution result of mergeData, that is, the formal merging of data options
Is in the mergeData function. Let's take a look at the implementation of the mergeData function
function mergeData (to: Object, from: ?Object): Object { if (!from) return to let key, toVal, fromVal // Through reflect Ownkeys can get the Symbol attribute const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from) for (let i = 0; i < keys.length; i++) { key = keys[i] // in case the object is already observed... if (key === '__ob__') continue toVal = to[key] fromVal = from[key] if (!hasOwn(to, key)) { // The parent class option does not exist in the subclass. Add the parent class option to the subclass and make it responsive set(to, key, fromVal) } else if ( // Parent class options already exist in subclasses, are not equal, and are all ordinary objects. Recursion is required toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { mergeData(toVal, fromVal) } } return to }
By analyzing the mergeData method, it is found that the merging principle of data options is to merge the options of the parent class into the sub class. If there is a conflict between the options of the parent class and the sub class (for example, there are object attributes, inconsistent data types or different values), the options of the sub class will be retained.
If the options are nested, recursive calls are required for merging.
Knowledge points
The Symbol type introduced in ES6 cannot be enumerated when it is used as an object attribute, and object Getownpropertynames() also does not return the properties of the Symbol object, but you can use object Getownpropertysymbols() to get the Symbol property
In the mergeData method above, use reflect The ownkeys method can obtain all attributes including the Symbol object attribute. Its return value is equal to its return value, which is equal to object getOwnPropertyNames(target). concat(Object.getOwnPropertySymbols(target)).
Finally, in development, when creating Vue instances, the data option in the options provided can be an object, while when creating components, the data option must be a function. It can be understood that the purpose of creating components is to realize service reuse. When data is used as a function, in
When creating multiple component instances, the data between component instances will not be applied to each other, because the data of each component instance is a copy of the data in the component template.
Vue constructor built-in option merge
Vue constructor has built-in options of components, directive and filter. Both Vue root instance and component instance need to be combined with these options.
// core/util/options.js // Vue default options are merged into each Vue instance ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets }) function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): Object { const res = Object.create(parentVal || null) // Create a 🈳 Object to make its prototype point to the resource options of the parent class. The built-in components, instructions and filters need to be called by prototype if (childVal) { // Verify the validity of options in the development environment. These options need to be an object process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res } }
The merging strategy of these options is also relatively simple. First, create an empty object, and the prototype of the control object points to the resource options of the parent class, and then assign the options of the child class to the whole empty object