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
  • ifpropsIt’s an array, for example['nick-name']After being converted into{ nickName: { type: null } }
  • ifpropsIs 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