Vue.use
The document
It is defined in SRC /core/global-api/use.js
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
const args = toArray(arguments.1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
Copy the code
Use the first argument passed in can be an object or a function. First get or create the _installedPlugins array and determine if the passed parameters are in the array to prevent multiple registrations. Next, convert the parameters other than the first parameter to an array; And add the Vue constructor to the beginning of the array. If the first argument passed in is an object and the install method is available, call Install using the apply method with an array element and this pointing to the passed object. If the first argument is a function, apply calls the argument as an array element, and this points to NULL. Finally, return to Vue
That is, if the first argument to vue. use is an object and there is an install method, the first argument to the install method is Vue, and inside this refers to the object
const installTest = {
name: 'installTest'.install(Vue, ... args){
console.log(this.name, args)
}
}
Vue.use(installTest, 1.2.3)
// Print installTest, [1, 2, 3]
Copy the code
If the first argument to Vue. Use is a function, the function’s first argument is also Vue, and this points to null
const fnTest = function (Vue, ... args){
console.log(this, args)
}
Vue.use(fnTest, 1.2.3)
Print null, [1, 2, 3]
Copy the code
Vue.mixin
Defined in SRC /core/global-api/mixin.js
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
Copy the code
The mergeOptions call adds the passed object to vue.options according to some merge strategy and returns Vue
export function mergeOptions (
parent: Object,
child: Object, vm? : Component) :Object {
if (typeof child === 'function') {
child = child.options
}
// normalizing props
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
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
for (key in parent) {
mergeField(key)
}
for (key in child) {
if(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Copy the code
The first is to standardize the props, inject, and directives, as described here
function normalizeProps (options: Object, vm: ? Component) {
const props = options.props
if(! props)return
const res = {}
let i, val, name
// props: ['name', 'nick-name']
if (Array.isArray(props)) { / / array
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
// nick-name -> nickName
name = camelize(val)
res[name] = { type: null}}else if(process.env.NODE_ENV ! = ='production') {
warn('props must be strings when using array syntax.')}}}else if (isPlainObject(props)) { / / object
for (const key in props) {
val = props[key]
name = camelize(key)
// Val can be an object or a constructor (name: Boolean)
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if(process.env.NODE_ENV ! = ='production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}. `,
vm
)
}
options.props = res
}
Copy the code
- if
props
It’s an array, for example['nick-name']
After being converted into{ nickName: { type: null } }
- if
props
Is an object, before and after the conversion
/ / before the conversion
{
nick-name: Boolean.name: { type: String}}/ / after the transformation
{
nickName: { type: Boolean },
name: { type: String}}Copy the code
Back in mergeOptions, after the normalization is complete, if the child does not have a _base attribute, merge child.extends and child.mixins into the parent via mergeOptions
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)
}
}
}
Copy the code
SRC /core/global-api/index.js mounts the _base attribute to Vue. Options, Vue. Options. _base = Vue. In the _init method, mergeOptions is called to merge vue. options into the options of the root instance. When a component VNode is created, the vue. extend constructor is called to create the component instance. In vue. extend, mergeOptions is also called to merge vue. options into the options constructor. MergeOptions merge child with _base; Only the original child object does not have one; So only the extends and mixins of the original child are merged into the parent via mergeOptions
Next, start the merge logic
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
Copy the code
The mergeField function traverses all of the parent’s keys and the rest of the child’s keys. Strats is an object that stores various cache policies
strats = {
props: xxx,
methods: xxx,
computed: XXX, life cycle: XXX,... }Copy the code
If there is no corresponding key in the Strats object, the default policy defaultStrat is called
const defaultStrat = function (parentVal: any, childVal: any) :any {
return childVal === undefined
? parentVal
: childVal
}
Copy the code
The default policy is child first. After the above merge strategy, assign the combined property value to options and return
This section describes several merge strategies
Lifecycle merge strategy
const LIFECYCLE_HOOKS = [
'beforeCreate'.'created'.'beforeMount'.'mounted'. ] ; LIFECYCLE_HOOKS.forEach(hook= > {
strats[hook] = mergeHook
})
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function|?Array<Function>
): ?Array<Function> {
const res = childVal
? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal]
: parentVal
return res ? dedupeHooks(res) : res
}
Copy the code
Merges life cycles into arrays based on whether parent and child exist. Return this array after dedupeHooks are used to undo the array
Data merge Strategy
strats.data = function (parentVal: any, childVal: any, vm? : Component): ?Function {
if(! vm) {if (childVal && typeofchildVal ! = ='function') { 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
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
Copy the code
MergeDataOrFn is called regardless of whether there is a VM. Vue.extend and vue. mixin trigger mergeOptions functions that have no VM attributes and will warn if the data passed is not a function in a development environment. The constructor of the child component instance is created by vue.extend, so vm attributes are not passed in either
export function mergeDataOrFn (parentVal: any, childVal: any, vm? : Component): ?Function {
if(! vm) {if(! childVal) {return parentVal
}
if(! parentVal) {return childVal
}
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this.this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this.this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
Copy the code
If no VM is passed in, a function called mergedDataFn is returned, which is called during instance creation. MergeData is called internally and all attributes of childVal and parentVal are passed in.
If the VM has a value, return the mergedInstanceDataFn function, which is called during instance creation, first getting all the properties of childVal and parentVal; If childVal has a value, mergeData is called and parentVal is returned. In fact, the vm only happens during the root instance creation, so it is ok to return the object directly
function mergeData (to: Object.from:?Object) :Object {
if (!from) return to
let key, toVal, fromVal
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if(! hasOwn(to, key)) { set(to, key, fromVal) }else if( toVal ! == fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { mergeData(toVal, fromVal) } }return to
}
Copy the code
Get an array of property names from, iterate through the array, and if the current key is not in to, call vue.prototype. $set to add the value to to. If the attribute values are all objects and are not equal, mergeData is recursively called, eventually returning to.
Vue.$nextTick
The code is defined in SRC /core/util/next-tick.js, and the timerFunc variable is initialized before the nextTick method is defined
export let isUsingMicroTask = false
const callbacks = []
let pending = false
function flushCallbacks () {}
let timerFunc
// If the environment supports Promise
if (typeof Promise! = ='undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () = > {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// If the current environment supports MutationObserver
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)) {
// If the current environment supports setImmediate
timerFunc = () = > {
setImmediate(flushCallbacks)
}
} else {
// None of the above is supported
timerFunc = () = > {
setTimeout(flushCallbacks, 0)}}Copy the code
TimerFunc is always a function, but the content of the function is set to a different value depending on how the current environment supports the API
If promises are supported, create a Promise instance and set isUsingMicroTask to true to represent microtasks. Set the callback function in timerFunc to the then function for flushCallbacks
If Promise is not supported but MutationObserver is supported, create a listener with a callback function called flushCallbacks and a text node to listen on the text node. IsUsingMicroTask is also set to true, representing microtasks. The timerFunc function internally modifies the properties of the text node to trigger the callback.
If neither of the above microtasks is supported, but setImmediate is supported, the timerFunc function internally calls the setImmediate method. The callback is called flushCallbacks, which is a macro task
If the above three methods are not supported, timerFunc creates an internal timer. The callback function is flushCallbacks and the delay time is 0
When vue. $nextTick is called, the code is as follows
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) { pending =true
timerFunc()
}
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= > {
_resolve = resolve
})
}
}
Copy the code
Start by creating a function and adding it to the callbacks; If pending is false, set pending to true so that only one flushCallbacks function is waiting to execute at any one time. That is, calling nextTick multiple times at the same time only adds a function to the Callbacks, not timerFunc multiple times.
The timerFunc function is then called, which pushes the flushCallbacks function into the queue (microtasks take precedence). If no CB is passed in, a Promise is returned. Within the Promise, resolve is assigned to _resolve. That is, the state of the Promise will only be changed when _resolve() is called, which also happens in flushCallbacks.
The flushCallbacks function is as follows
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
Copy the code
Set pending to false to indicate that there are no flushCallbacks in the queue. The next step is to iterate through and execute all the callbacks in the Callbacks array.
callbacks.push(() = > {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')}}else if (_resolve) {
_resolve(ctx)
}
})
Copy the code
First check if cb exists, if cb exists, call cb, and this refers to CTX; If cb is not available, _resolve is called and CTX is passed in.
In summary, vue. $nextTick sets the timing of callback execution based on the API supported by the current environment, with microtasks taking precedence.
For a final quiz, consider printing the results
new Vue({
el: '#app'.template: `
{{title}}
`,
data () {
return {
title: 'I am the title'}},methods: {
change () {
this.$nextTick(() = > {
console.log(1.this.title)
})
this.title = 'test'
this.$nextTick(() = > {
console.log(2.this.title)
})
}
}
})
Copy the code
The printed result above is
1, 'test'
2, 'test'
Copy the code
Other API principles corresponding to the article detailed analysis, directly post links
Vue.prototype.$set
Vue source code (two) response type principle
Vue.extend
Vue source code (a) how to create VNode
Vue.component
Vue source code (eight) asynchronous component principle