The target

Learn more about how the following global apis are implemented.

  • Vue.use

  • Vue.mixin

  • Vue.component

  • Vue.filter

  • Vue.directive

  • VUe.extend

  • Vue.set

  • Vue.delete

  • Vue.nextTick

The source code interpretation

As you can see from the introduction to the source code directory structure in the first article in the series, Vue Source Code Interpretation (1), most of Vue’s global API implementations are in the/SRC /core/global-api directory. The source reading entry for these global apis is in the/SRC /core/global-api/index.js file.

The entrance

/src/core/global-api/index.js

/** * initialize Vue's global apis, such as: * Default: Vue. Vue.util. Xx * Vue.set, vue.delete, vue.nexttick, vue.Observable * Vue.options.components, vue.options. directives, vue.options. Directives, vue.options. Filters, vue.options._base * Use, vue.extend, vue.mixin, Vue.component, vue.directive, vue.filter * */
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  // Many default configurations for Vue
  configDef.get = () = > config

  if(process.env.NODE_ENV ! = ='production') {
    configDef.set = () = > {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.')}}// Vue.config
  Object.defineProperty(Vue, 'config', configDef)

  /** * Expose some tools, do not use them easily, you know the tools, and know the risks */
  Vue.util = {
    // Warning log
    warn,
    // Similar options merge
    extend,
    // Merge options
    mergeOptions,
    // Set the response
    defineReactive
  }

  // Vue.set / delete / nextTick
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // The response method
  Vue.observable = <T>(obj: T): T= > {
    observe(obj)
    return obj
  }

  // Vue.options.compoents/directives/filter
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type= > {
    Vue.options[type + 's'] = Object.create(null)})// Mount the Vue constructor to vue.options._base
  Vue.options._base = Vue

  // Add built-in components to the Vue.options.components, such as keep-alive
  extend(Vue.options.components, builtInComponents)

  // Vue.use
  initUse(Vue)
  // Vue.mixin
  initMixin(Vue)
  // Vue.extend
  initExtend(Vue)
  // Vue.component/directive/filter
  initAssetRegisters(Vue)
}

Copy the code

Vue.use

/src/core/global-api/use.js

/** * defines Vue. Use, which is responsible for installing the plug-in for Vue. * 1@param {*} The plugin install method or object * containing the install method@returns Vue instance * /
Vue.use = function (plugin: Function | Object) {
  // The list of plugins that have been installed
  const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
  // Check whether the plugin is already installed
  if (installedPlugins.indexOf(plugin) > -1) {
    return this
  }

  // Put the Vue constructor in the first argument position and pass those arguments to the install method
  const args = toArray(arguments.1)
  args.unshift(this)

  if (typeof plugin.install === 'function') {
    // If plugin is an object, execute its install method to install the plug-in
    plugin.install.apply(plugin, args)
  } else if (typeof plugin === 'function') {
    // Install the plugin using the direct plugin method
    plugin.apply(null, args)
  }
  // Add the newly installed plug-in to the plugin list
  installedPlugins.push(plugin)
  return this
}

Copy the code

Vue.mixin

/src/core/global-api/mixin.js

/** * defines Vue. Mixin, which is responsible for the global mixin option, and affects all Vue instances created later, which will incorporate the global mixin option *@param {*} Mixin Vue configuration object *@returns Return Vue instance */
Vue.mixin = function (mixin: Object) {
  // Merge mixin objects on Vue's default configuration
  this.options = mergeOptions(this.options, mixin)
  return this
}

Copy the code

mergeOptions

src/core/util/options.js

/** * Merge two options. When the same configuration item appears, the child option overwrites the configuration of the parent option */
export function mergeOptions (
  parent: Object,
  child: Object, vm? : Component) :Object {
  if(process.env.NODE_ENV ! = ='production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  // standardise props, inject, and directive options
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Handles extends and mixins on the original Child object, executing mergeOptions, respectively, to merge these inherited options into the parent
  // mergeOptions handles objects that have the _base attribute
  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
  // Iterate over the parent option
  for (key in parent) {
    mergeField(key)
  }

  If the parent option does not have the configuration, merge it, otherwise skip it, because the case of the parent having the same property was already handled when the parent option was processed above, using the value of the child option
  for (key in child) {
    if(! hasOwn(parent, key)) { mergeField(key) } }// Merge options, childVal takes precedence over parentVal
  function mergeField (key) {
    // If strat is a merge policy function, childVal overwrites parentVal
    const strat = strats[key] || defaultStrat
    // If childVal exists, childVal is preferred; otherwise, parentVal is used
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Copy the code

Vue.component, vue.filter, vue.directive

/src/core/global-api/assets.js

These three API implementations are special, but the principles are similar, so they are implemented together.

const ASSET_TYPES = ['component'.'directive'.'filter']

/** * Define the Vue.component, vue.filter, and vue.directive methods * The three methods do similar things, For example, Vue.component(compName, {xx}) as a result, this.options.components.com pName = component constructor * ASSET_TYPES = [' component ', 'directive', 'filter'] * /
ASSET_TYPES.forEach(type= > {
  /** * For example: Vue.component(name, definition) *@param {*} id name
   * @param {*} Definition Component constructor or configuration object *@returns Returns the component constructor */
  Vue[type] = function (
    id: string,
    definition: Function | Object
  ) :Function | Object | void {
    if(! definition) {return this.options[type + 's'][id]
    } else {
      if (type === 'component' && isPlainObject(definition)) {
        // If there is a name in the component configuration, use it; otherwise, use the ID
        definition.name = definition.name || id
        Extend = vue.extend; // extend = vue.extend; // extend = vue.extend; // extend = vue.extend; // extend = vue.extend;
        definition = this.options._base.extend(definition)
      }
      if (type === 'directive' && typeof definition === 'function') {
        definition = { bind: definition, update: definition }
      }
      // this.options.components[id] = definition
      // Incorporate globally registered components into the components of each component's configuration object via mergeOptions at instantiation time
      this.options[type + 's'][id] = definition
      return definition
    }
  }
})

Copy the code

Vue.extend

/src/core/global-api/extend.js

/** * 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

/** * extend the subclass based on Vue, which also supports further extensions * extension can pass some default configurations, just like Vue has some default configurations * default configurations are merged if they conflict with the base class */
Vue.extend = function (extendOptions: Object) :Function {
  extendOptions = extendOptions || {}
  const Super = this
  const SuperId = Super.cid

  /** * Use the cache, and return the constructor in the cache if it exists * When can the cache be used? * This cache is enabled if you use the same configuration item (extendOptions) for multiple calls to vue.extend */
  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
  if (cachedCtors[SuperId]) {
    return cachedCtors[SuperId]
  }

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

  // Define the Sub constructor, just like the Vue constructor
  const Sub = function VueComponent(options) {
    / / initialization
    this._init(options)
  }
  // Inherit Vue by prototype inheritance
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  // Option merge, merge Vue configuration items into their own configuration items
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  // Keep track of your base class
  Sub['super'] = Super

  // Initialize the props configuration proxy to sub.prototype. _props
  // It is accessible within the component using this._props
  if (Sub.options.props) {
    initProps(Sub)
  }

  // Initialize computed and delegate the computed configuration to the sub.prototype object
  // The component can be accessed using this.computedKey
  if (Sub.options.computed) {
    initComputed(Sub)
  }

  // Define three static methods: extend, mixin, and use, which allow subclasses to be constructed on top of Sub
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  // Define static methods for Component, filter, and directive
  ASSET_TYPES.forEach(function (type) {
    Sub[type] = Super[type]
  })

  // Recursive component principle. If the component has the name attribute set, it registers itself with its components options
  if (name) {
    Sub.options.components[name] = Sub
  }

  // Keep references to base class options when extending.
  // Later, when instantiating, we can check whether the Super option has been updated
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  / / cache
  cachedCtors[SuperId] = Sub
  return Sub
}

function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

Copy the code

Vue.set

/src/core/global-api/index.js

Vue.set = set
Copy the code

set

/src/core/observer/index.js

$set val * If target is an object and the key does not already exist, then set a response for the new key and perform dependency notification */
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 (array, idx, val)
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  Vue.set(obj, key, val)
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // Cannot add dynamic add response attribute to Vue instance or $data, one of the uses of vmCount,
  // this.$data ob.vmCount = 1, indicating the root component, other subcomponents vm
  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
  }
  // Target is not a reactive object. New properties are set, but not reactive
  if(! ob) { target[key] = valreturn val
  }
  // Define a new attribute for the object, set the response via defineReactive, and trigger the dependency update
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

Copy the code

Vue.delete

/src/core/global-api/index.js

Vue.delete = del
Copy the code

del

/src/core/observer/index.js

/** * deletes the target object's array of specified keys * via the splice method. The object deletes the specified key * via the delete operator and performs the dependency notification */
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)}`)}// If target is an array, the splice method deletes the element with the specified index
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__

  // Avoid deleting Vue instance attributes or $data
  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
  }
  // If the attribute does not exist, end directly
  if(! hasOwn(target, key)) {return
  }
  // Delete the properties of an object with the delete operator
  delete target[key]
  if(! ob) {return
  }
  // Perform dependency notifications
  ob.dep.notify()
}

Copy the code

Vue.nextTick

/src/core/global-api/index.js

Vue.nextTick = nextTick
Copy the code

nextTick

/src/core/util/next-tick.js

For a more detailed analysis of the nextTick method, see Vue source code Interpretation (4) — Asynchronous Update in the previous article.

const callbacks = []
/** * Do two things: * 1, use the try catch flushSchedulerQueue packaging function, and then put it in the callbacks array * 2, if the pending to false, * If pending is true, flushCallbacks are already in the browser's task queue. * If pending is true, flushCallbacks are already in the browser's task queue. Pending is set to false again, indicating that the next flushCallbacks can enter the browser's task queue@param {*} Cb Receives a callback function => flushSchedulerQueue *@param {*} CTX context *@returns * /
export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  // Use the callbacks array to store the wrapped CB function
  callbacks.push(() = > {
    if (cb) {
      // Wrap the callback function with a try catch to catch errors
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    // Execute timerFunc and put the flushCallbacks function in the browser's task queue (the preferred microtask queue)
    timerFunc()
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}

Copy the code

conclusion

  • Interviewer: What did Vue.use(plugin) do?

    A:

    To install a plugin, perform the install method provided by the plugin.

    • First determine if the plug-in has already been installed

    • If no, install the plug-in using the install method provided by the plug-in. The plug-in decides what to do


  • Interviewer: What did Vue.mixin(Options) do?

    A:

    Is responsible for merging options configurations on Vue’s global configuration. The global configuration is then merged into the component’s own configuration when each component generates a VNode.

    • Standardizes the formatting of props, inject, and directive options on the options object

    • Handle extends and mixins on Options, incorporating them into the global configuration, respectively

    • Then merge the options configuration with the global configuration. If the options conflict, the options configuration overrides the global configuration


  • Interviewer: What did Vue.com Ponent (compName, Comp) do?

    A:

    Responsible for registering global components. The component configuration is registered with the global components option (options.com components), and then the sub-components will merge the global Components option into the local Components configuration when the vNode is generated.

    • If the second argument is empty, it indicates the component constructor that gets compName

    • If Comp is a component configuration object, use the vue. extend method to get the component constructor, otherwise go straight to the next step

    • Information on the global configuration Settings components, this.options.components.com pName = CompConstructor


  • Interviewer: What does vue. directive(‘my-directive’, {xx}) do?

    A:

    Register the mY-Directive globally, and then each of the child directives merges the global directives into the local directives when the VNode is generated. The principle is the same as Vue.com Ponent method:

    • If the second argument is empty, the configuration object for the specified directive is obtained

    • {bind: second argument, update: second argument}}

    • Then set the directive configuration object to the global configuration, this.options. Directives [‘my-directive’] = {xx}


  • What does vue.filter (‘my-filter’, function(val) {xx}) do?

    A:

    It is responsible for registering the mY-filter globally, and each subcomponent then merges the global filters option into the local filters option when generating the VNode. The principle is:

    • If the second argument is not provided, the callback function for the My-filter filter is obtained

    • Filters [‘my-filter’] = function(val) {xx} if the second argument is provided, set this.options. Filters [‘my-filter’] = function(val) {xx}.


  • Interviewer: What did Vue.extend(Options) do?

    A:

    Extend creates a subclass based on Vue, and the options parameter is the default global configuration for that subclass, just like the default global configuration for Vue. So extending a subclass with vue. extend is useful because it has built-in common configurations that can be used by subclasses of the subclass.

    • Define the subclass constructor, which, like Vue, calls _init(options).

    • Merge the Vue configuration and options. If the options conflict, the options overwrite the Vue configuration

    • Define a global API for a subclass with the value Vue global API, such as sub.extend = super.extend, so that the subclass can also extend to other subclasses

    • Return subclass Sub


  • Interviewer: What did Vue.set(target, key, val) do

    A:

    Due to the Vue cannot detect common new property (such as this. MyObject. NewProperty = ‘hi’), so by the Vue. Set to add a response to objects of type property, You can ensure that the new property is also responsive and triggers view updates.

    • Set (array, idx, val). Internally, the splice method is used to implement responsive updates

    • Set (obj, key,val) => obj[key] = val

    • You cannot dynamically add root-level responsive data to a Vue instance or $data

    • Vue.set(obj, key, val). If obj is not a reactive object, obj[key] = val will be executed, but no reactive processing will be done

    • Vue.set(obj, key, val), add a new key to the responsive object obj, set the responsive using defineReactive method and trigger the dependency update


  • Interviewer: What did Vue.delete(target, key) do?

    A:

    Delete the property of the object. If the object is reactive, make sure the deletion triggers an update to the view. This method is mainly used to get around the limitation that Vue cannot detect when a property is removed, but you should rarely use it. Of course, the same cannot be said for root level responsive attributes.

    • Vue.delete(array, idx), delete the element with the specified subscript, internally by splice

    • Delete an attribute on a responsive object: vue.delete (obj, key), internally delete obj.key, and then perform a dependency update


  • Interviewer: What did Vue.Nexttick (CB) do?

    A:

    NextTick (cb) ¶ The nextTick(cb) method is used to delay the execution of the cb callback function. This is used to retrieve DOM data immediately after this. Key = newVal is changed:

    this.key = 'new val'
    
    Vue.nextTick(function() {
      // The DOM is updated
    })
    Copy the code

    Its internal implementation process is:

    • This.key = ‘new val, triggering the dependency notification update, placing the watcher responsible for the update in the watcher queue

    • Put the function that refreshes the Watcher queue into the Callbacks array

    • Put a function to refresh the array of Callbacks in the browser’s asynchronous task queue

    • NextTick (cb) to jump the queue and put the CB function into the callbacks array

    • A function to refresh the array of Callbacks is executed at some point in the future

    • It then executes a number of functions in the Callbacks array, triggering the execution of watcher.run and updating the DOM

    • Since the CB function is placed later in the Callbacks array, this ensures that the DOM update is completed before the CB function is executed

Form a complete set of video

Vue source code Interpretation (5) — Global API

Please focus on

Welcome everyone to follow my gold mining account and B station, if the content has to help you, welcome everyone to like, collect + attention

link

  • Vue source code interpretation (1) – preface

  • Vue source code interpretation (2) — Vue initialization process

  • Vue source code interpretation (3) – response principle

  • Vue source code interpretation (4) — asynchronous update

  • Vue source code Interpretation (5) — Global API

  • Vue source code interpretation (6) — instance method

  • (7) — Hook Event

  • Vue source code interpretation (8) — compiler parsing

  • Vue source code interpretation (9) — compiler optimization

  • Vue source code interpretation (10) — compiler generation rendering function

  • (11) — Render helper

  • Vue source code interpretation (12) — patch

Learning exchange group

link