preface

When using Vue framework for development, it is inevitable to use some global-API or instance API (in essence, also call global API, equivalent to alias) to solve some problems encountered in the project, such as: This.$set(), this.$nextTick(), etc. There are also some global apis that have not been used in the right context because they are probably not understood. Let’s take a look at the source code level to see what these global apis do and how they work.

The following is a comparison between the official documentation directory and the source directory:


Deep source

InitGlobalAPI () method

File location: SRC \core\global-api\index.js

Initialize the global API, such as vue.util = {… }, vue. options = {… }, Vue. [set | del | nextTick | observables | use | mixin | the extend | component | directive | filter], details please see the following comments.

// Initialize the entry to the global API
export function initGlobalAPI (Vue: GlobalAPI) {
  // The default Vue configuration is config
  const configDef = {}
  configDef.get = () = > config

  // Overwriting with vue. config = {} is not allowed
  if(process.env.NODE_ENV ! = ='production') {
    configDef.set = () = > {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.')}}// Proxy the config item to Vue. You can access it through Vue. Config
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // Some tool methods are exposed
  Vue.util = {
    // Warning log
    warn,
    // extend (to: Object, _from: ? Object), which copies properties on the _from Object to the TO Object
    extend,
    // Merge configuration items
    mergeOptions,
    // Set getters and setters for dependency collection and dependency update notifications, respectively
    defineReactive
  }

  // The global set method handles additions or modifications to array elements or object attributes
  Vue.set = set
  // The global delete method deletes an array element or object attribute
  Vue.delete = del
  // The global nextTick method, which relies on browsing asynchronous task queues
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  // The global Observable method, essentially the observe method, transforms the received object into a responsive object
  Vue.observable = <>(obj: T): T => {observe(obj) return obj} {/* Set the specified configuration item for global options vue. options = {components:{}, directive: {}, filters:{} } */} Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with In Weex's multi-instance scenarios. {/* Assign Vue to vue.options._base, Vue. Options. _base = Vue {/* builtInComponents KeepAlive register KeepAlive with the global components configuration, That is, it can be used directly globally<keep-alive></keep-alive>*/} extend(Vue.options.components, BuiltInComponents) {/* Initializes vue. use method */} initUse(Vue) {/* Initializes vue. mixin method */} initMixin(Vue) {/* Initializes vue. extend method */} InitExtend (Vue) {/* Initializes vue.component, vue.directive, vue.filter methods */} initAssetRegisters(Vue)}Copy the code

Vue. Set () method

File location: SRC \core\observer\index.js

The vm.$set on the instance is an alias to the global vue. set. The purpose is to add a property to the reactive object and ensure that the new property is also reactive, replacing the old value with the new value if the key already exists and triggering a view update.

export function set (target: Array<any> | Object, key: any, val: any) :any {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}Vue. Set (arr, index, value
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // Essentially implement element substitution via the rewritten splice method
    target.splice(key, 1, val)
    return val
  }

  Set (this, newKey, value) to set dynamic, responsive properties */ 

 // If there is a key attribute on the current object that does not belong to the prototype object, the old value is directly updated
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }

  // Check whether the current object is processed by an observer. If __ob__ exists, it is processed; otherwise, it is a normal object
  const ob = (target: any).__ob__
  // Avoid adding properties or modifying property values directly to the Vue instance or root component's $data at runtime
  if(target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! = ='production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  Vue. Set (obj, key, value) can be set successfully for ordinary objects, but it does not have responsiveness
  if(! ob) { target[key] = valreturn val
  }
  // Set getters and setters for new properties, collect dependencies when read, and notify dependencies when set
  defineReactive(ob.value, key, val)
  // Make dependency update notifications directly
  ob.dep.notify()
  return val
}
Copy the code

Vue. Delete () method

File location: SRC \core\observer\index.js

The vm.$delete on the instance is an alias for the global vue. delete method. The purpose is to ensure that deleting an object’s property triggers updating the view if the object is responsive.

export function del (target: Array<any> | Object, key: any) {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)}// Delete array elements: Use the rewritten splice method to delete elements
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }

  // Delete object properties
  const ob = (target: any).__ob__
  if(target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV ! = ='production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // The current key does not exist on the current object
  if(! hasOwn(target, key)) {return
  }
  // Delete object attributes with the delete operator
  delete target[key]
  // If it is a common object, no dependency update notification is made after the property is deleted
  if(! ob) {return
  }
  // If the object is responsive, the dependency update notification is made after the attribute is deleted
  ob.dep.notify()
}
Copy the code

Vue. NextTick () method

File location: SRC \core\util\next-tick.js

Defer the callback until after the next DOM update cycle, use it immediately after modifying the data, and then wait for DOM updates.

As with the global method vue.nexttick, the difference is that the callback’s this is automatically bound to the instance calling it.

NextTick is covered in detail in previous articles, and you can check it out in more detail with nextTick.

export function nextTick(cb? :Function, ctx? :Object) {
  let _resolve

  // Wrap cb in an anonymous function and store it in callbacks
  callbacks.push(() = > {
    // Cb may be the flushSchedulerQueue function passed internally by Vue or a user-defined function passed externally by a user. Therefore, try catch is required for CB to facilitate exception catching
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      // cb does not exist. The default is _resolve = resolve
      _resolve(ctx)
    }
  })

  // Pending = false; timerFunc()
  if(! pending) { pending =true
    Execute the flushCallbacks() function using the browser's asynchronous task
    timerFunc()
  }

  // When the CB function does not exist and promises are supported, you need to provide a default function, the resolve method in Promise
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code

Vue. Observables () method

File location: SRC \core\observer\index.js

The observe() method, which is called to make an object responsive, is typically used internally by Vue to process objects returned by the data function.

The returned object can be used directly in rendering functions and computed properties, and will trigger an update when changes occur.

Observe Observe. Observe Observe observe observe observe observe observe observe observe observe observe Observe Observe Observe.

export function observe (value: any, asRootData: ? boolean) :Observer | void {
  if(! isObject(value) || valueinstanceof VNode) {
    return
  }
  let ob: Observer | void
  // Value is already handled by an Observer and returns the last __ob__ instance directly
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && ! value._isVue ) {// Instantiate an Observer instance for value
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
Copy the code

InitUse () method – initializes vue.use

File location: SRC \core\ global-api-use.js

If the plug-in is an object, the install method must be provided.

If the plug-in is a function and there is no install attribute on the function object, it is used as the install method, and if there is an Install attribute on the function object, the install attribute is used preferentially.

When the install method is called, Vue is passed in as an argument, and the method needs to be called before calling new Vue().

export function initUse (Vue: GlobalAPI) {
  Install: (Vue)=>{}}
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // Avoid double registration of plug-ins
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments.1)
    // Make Vue the first parameter in the plug-in parameters
    args.unshift(this)
    // Plugin object has install attribute, value is a function, bind this with apply and call
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // Plugin is itself a function called by apply
      plugin.apply(null, args)
    }
    // Save the plugins to the vue. installedPlugins array
    installedPlugins.push(plugin)
    return this}}Copy the code

InitMixin () method — initializes vue.mixin

File location: SRC \core\global-api\mixin.js

Global registration is a mixin that affects all Vue instances created after registration. This is not recommended. The mixins option on component configuration items is still the most used option.

The essence is to merge configuration items, the mergeOptions() method.

export function initMixin (Vue: GlobalAPI) {
  // Mix configuration items into the component configuration
  Vue.mixin = function (mixin: Object) {
    // Essentially merge configuration items
    this.options = mergeOptions(this.options, mixin)
    return this}}Copy the code

MergeOptions () method

File location: SRC \core\util\options.js

The main idea is to merge two option objects into a new object, which is the core of instantiation and inheritance.

export function mergeOptions (
  parent: Object,
  child: Object, vm? : Component) :Object {
  if(process.env.NODE_ENV ! = ='production') {
    checkComponents(child)
  }

  // If child is a function, send it to the function to get the configuration item
  if (typeof child === 'function') {
    child = child.options
  }

  // Standardize the option configuration
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Extends and mixins on suboptions
  // And must be the original configuration object, that is, no merged configuration object
  // Every configuration object that is merged has the _base attribute
  if(! child._base) {// The extents of components are the same as vue. extend, extents to facilitate the extension of single-file components
    // var CompA = { ... } , var CompB = { extends: CompA, ... }, B inherits from A
    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) {
    // Option merge policy
    // The default policy is to override the parent value if the child has a value, otherwise use the parent value
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
Copy the code

Vue. The extend () method

File location: SRC \core\global-api\extend.js

Using the base Vue constructor, create a subclass whose argument is an object containing component options. The data option is a special case because it must be a function in vue.extend ().

export function initExtend (Vue: GlobalAPI) {
  /** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. * /
  Vue.cid = 0
  let cid = 1

  /** * Class inheritance * uses vue. extend to create a subclass with an object containing component options */
  Vue.extend = function (extendOptions: Object) :Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // Cache: If the same mixin configuration item is used, the cached value is used directly if there is an existing one in the cache
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // Verify the component name
    const name = extendOptions.name || Super.options.name
    if(process.env.NODE_ENV ! = ='production' && name) {
      validateComponentName(name)
    }

    Function Vue (options) {this._init(options)} */
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // Set the subclass stereotype to the base class stereotype
    Sub.prototype = Object.create(Super.prototype)
    // Specify the subclass constructor as itself
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // Merge base class options and incoming config items into subclass config items
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // Projects props to a subclass, which can be accessed directly through the this.props form
    if (Sub.options.props) {
      initProps(Sub)
    }
    // Assign computed proxy to a subclass, where it can be accessed directly through the this.computed form
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    // Assign extension methods from the base class to subclasses so that subclasses have their own extension capabilities
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    // How components recursively call themselves
    // { name:'comp', components: { Comp } }
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    // Cache subclass
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
Copy the code

InitAssetRegisters () method

File location: SRC \core\global-api\assets.js

This method is responsible for initializing Vue.component(), vue.directive (), and vue.filter (), because these three apis are special in implementation but similar in principle. So we put it all in initAssetRegisters for initialization.

export function initAssetRegisters (Vue: GlobalAPI) {
  /** * Create asset registration methods. * Initialize vue.component.directive, vue.filter */
  ASSET_TYPES.forEach(type= > {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ) :Function | Object | void {
      // If the corresponding definition is not passed, return it directly
      if(! definition) {return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        // Verify the component name
        if(process.env.NODE_ENV ! = ='production' && type === 'component') {
          validateComponentName(id)
        }
        // If it is a component
        if (type === 'component' && isPlainObject(definition)) {
          // Set the name of the component. If the configuration object is options.name, take the first value passed in if it does not exist
          definition.name = definition.name || id
          Extend extends a new component subclass based on Definition, instantiating a component directly from new Definition () via vue.extend
          definition = this.options._base.extend(definition)
        }

         // If it is a command
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }

        // Place each configuration item in the corresponding configuration item of the root component
        // For example :{components:{ID: comonent}, cache :{ID: directive}, filters:{id: filter},... }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
Copy the code

conclusion

What does vue.use (Plugin) do?

  • Vue.useIt’s for installationVueThe plug-in
    • If the plug-in is an object, the install method must be provided
    • If the plug-in were a function, it would be used as the install method
    • When the Install method is called, Vue is passed in as an argument
  • This method needs to be called before calling new Vue()
  • When the install method is called multiple times by the same plug-in, the plug-in will only be installed once

What does vue.mixin (options) do?

  • Vue.mixin essentially calls mergeOptions(parent: Object, Child: Object, VM? : Component), which merges two option objects into a new object
  • First, standardize the props, Inject, and directives options
  • Handles extends and mixins on options and eventually merges them into the global configuration
  • Merge the Options configuration and global configuration. If options conflict, the Options configuration overrides the global configuration

PS: when using component configuration itemsmixinsWhen adding configuration items

  • Proceed with merge options as normal without conflicts
  • In addition to life cycle hook conflicts, which are called before component corresponding life cycle hook directives, component data takes precedence when conflicts occur in options such as Methods, Components, and directives

What does Vue.com Ponent (compName, Comp) do?

Vue.com Ponent is used to register global components.

Essentially registering the current component configuration with the global configuration’s Components option ($root.options.components), Each child component then merges the global components option into the local components configuration item when generating a VNode, automatically registering the component locally.

  • Vue.component(compName) Component constructor that obtains compName
  • If Comp is a component configuration object, use vue.extend to get the component constructor, otherwise proceed to the next step
  • Add the current component information to the global configurationThis.options.com ponents = {compName: CompConstructor, XXX}

What does vue. directive(‘my-directive’, definition) do?

Vue.directive is used to register or obtain global directives that will be merged into local directives for each child component when generating a Vnode. Such as this.options.directives = {directive: {XXX}}

  • If definition is empty, the configuration object for the specified directive is retrieved
  • ifdefinitionPhi is a function, then it will be inbindupdateTo configure the object{ bind: definition, update: definition }

What does vue. filter(‘my-filter’, definition) do?

Vue.filter is used to register or get global filters. The global filters options are merged into local filters options when each child component generates a Vnode, such as this.options.filters = {filters: {XXX}}.

  • If definition is empty, get the callback function for the my-filter
  • ifdefinitionIf yes, registerthis.options.filters['my-filter'] = definition

What does vue.extend (extendOptions) do?

Vue.extend creates a subclass based on Vue and generates new configuration items through mergeOptions(super. options, extendOptions) as the default global configuration for the subclass, so the subclass can also get its own subclass by extending ().

  • Define a subclass constructor that, like the base class Vue, calls this._init(options).
  • Combine vue. options and extendOptions. If the options conflict, extendOptions will overwrite vue. options
  • Define and for subclassesVueSame globalAPI, such asSub.extend = Super.extend
  • Return subclass Sub outward

Vue. Set (target, key, val) and Vue. Delete (target, key).

Vue. Set (target, key, val) — Adds or updates the corresponding attribute or element value

  • Add a new property to the reactive object, set the getter and setter for the new property, collect dependencies when read, and notify dependencies when set
  • If target is an array, the key needs to be the corresponding index in the array, essentially updating the array element with a rewrite of the splice method
  • If target is an object, the corresponding attribute data is updated directly
  • If target is not a reactive object, the operation succeeds, but there is no reactive
  • You cannot dynamically add root-level reactive data to a Vue instance or $data

Vue.delete(target, key) — Deletes the object’s property

  • If the target is a reactive object, make sure that deleting the corresponding property triggers updating the view
  • If the target is an array, the element is deleted using the overridden splice method
  • If target is an object, delete target[key] + ob.dep.notify() to delete and update the view

The articles

  • What does vUE initialization do?
  • How to understand vUE responsiveness?
  • How does vUE update asynchronously?