重读Vue源码系列六—— 逐步揭开Vue全局API实现的面纱

370 阅读3分钟

一、概述

我们看下Vue源码中文件的依赖顺序

未命名文件 (1).png

根据es6文件依赖的执行顺序,以上文件的执行顺序是src/core/instance/index.jssrc/core/index.jssrc/platforms/web/runtime/index.jssrc/platforms/web/entry-runtime-with-compiler.js

上篇重读Vue源码系列五—— 逐步揭开Vue构造函数实现原型属性的面纱Vue原型属性初始化的实现是在src/platforms/web/runtime/index.js中,根据执行顺序这篇将会讲src/core/index.js

先看下源码

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue) //会给 Vue 这个对象本身扩展全局的静态方法

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'
export default Vue

我们看到了initGlobalAPI(Vue),其实这行代码就是给 Vue 这个对象本身扩展全局的静态方法或者叫全局API

二、initGlobalAPI

我们看下initGlobalAPI的位置import { initGlobalAPI } from './global-api/index'

global-api目录就是关于全局API的代码,其实Vue的源码也是按照功能来归类代码的。 image.png

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  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.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

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

  // 2.6 explicit observable API
  // Vue.observable = <T>(obj: T): T => {
  //   observe(obj)
  //   return obj
  // }
    Vue.observable = obj => {
    observe(obj)
    return obj
  }

  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.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

通过上面的源码我们看到initGlobalAPI中实现了以下全局API:

  • Vue.util

扩展的util方法,这些方法并不会暴露到全局API

  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

image.png

  • Vue.set 会在Vue实例化之后讲解

  • Vue.delete 会在Vue实例化之后讲解

  • Vue.nextTick 会在Vue实例化之后讲解

  • Vue.observable 会在Vue实例化之后讲解

  • 选项/资源Vue.components、Vue.directives、Vue.filters 参考

const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
  Vue.options = Object.create(null) //创建一个干净的空对象
  
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  • Vue.options._base = Vue

用于在Weex在中判别Vue对象

  • 内置组件keep-alive extend(Vue.options.components, builtInComponents) 后面讲组件的时候会详细讲解

  • Vue.use initUse(Vue)实现Vue.use,主要功能是封装自定义的原型属性比如Vue.prototype.$Toas或者全局注册组件Vue.component的功能,方便使用者,比如我们用element-ui的时候Vue.use(ElementUI); 具体的看参考Vue.use详解

//源码
export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) { //Vue.use实现,支持函数或者对象
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))//防止重复注册
    if (installedPlugins.indexOf(plugin) > -1) { //如果已经注册,返回
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this) //this其实就是Vue
    if (typeof plugin.install === 'function') { //函数的情景
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

  • Vue.minx initMixin(Vue),内部用到了_init里面的mergeOptions,后面会写一篇Vue.minx的实现讲解,现在只要知道就行
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin) //this = Vue
    return this
  }
}
  • Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
export function initExtend (Vue: GlobalAPI) {

  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   * 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
   */
  Vue.extend = function (extendOptions: Object): Function {
   //代码省略了,详细的会在组件部分讲解
  }
}
  • initAssetRegisters(Vue)为Vue添加了ASSET_TYPES:component、directive、filter静态方法,定义全局组件、指令、过滤器, 源码如下

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

function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   * 组件 指令 过滤器
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

三、总结

通过本篇文章和上一篇重读Vue源码系列五—— 逐步揭开Vue构造函数实现原型属性的面纱的讲解,我们大概知道了Vue的全局API和原型属性实现的基本轮廓,现在只需要在大脑中有基本印象就行了。后面讲完实例化部分之后,会写几篇文章分别讲一下主要的API实现逻辑。

现在应该可以看懂下面的图了

风格-彩虹.png