迫于菜🐶 - Vue.js 源码(三)

923 阅读3分钟

往期章节

  1. 第一章-源码目录
  2. 第二章-项目构建

前面两章中,我们跟随 Vue.js 技术揭秘 学习了 Vue.js 的源码目录结构和项目构建,今天我们分析一下从 import Vue 开始,到底做了哪些事情来完成初始化的。

入口在哪里

Runtime + Compiler 版本的入口文件是 src/platforms/web/entry-runtime-with-compiler.js

此处不贴大篇幅代码,我们发现 Vue 实际上是 import Vue from './runtime/index' 导入而来,中间通过 Vue.prototype.$mount 挂载了 $mount 方法,对 Vue 对象做了一些扩展,最后 export default Vue 导出 Vue。

继续找到 './runtime/index' 文件,和刚才看到的类似,同样是通过 import Vue from 'core/index' 导入,中间在原型上挂载了 __patch__$mount方法,并且定义一些 Vue 的全局配置,最后导出了 Vue。

来到 'core/index' 文件,哈哈这里的写法和刚才的两个又是类似的,通过 import Vue from './instance/index' 导入,中间通过 initGlobalAPI(Vue) 初始化了 Vue 的全局 API,最后依然是通过 export default Vue 导出 Vue。

我佛了,脑海中响起了杨宗纬的《洋葱》,我剥掉了一层又一层,还没找到 Vue.js 的核心代码,我 tm 怒捶蝶式键盘(这是为了换 16 寸不择手段啊龟龟)以解心中之郁闷,心累了。

心累归心累,生活还得向钱看。继续往上找吧,打开 './instance/index'

啊终于揭开了 Vue 的面纱,快来看看到底是什么鬼。

这里 Vue 的定义是一个函数,在函数内部进行了一个 !(this instanceof Vue) 的判断,如果为 false,就会报一个 ' Vue is a constructor and should be called with the new keyword ' 的警告,也就是说必须要通过 new Vue 的方法去实例化 Vue ,这其实是 ES5 实现 class 的一种方法。

那有些同学可能会有问,为何 Vue 不用 ES6 的 Class 去实现呢?我们往后看这里有很多 xxxMixin 的函数调用,并把 Vue 当参数传入,我们随便挑两个看一下它们都是如何定义的。

// initMixin(Vue)
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
// ...
// stateMixin(Vue)
export function stateMixin (Vue: Class<Component>) {
// ...
Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
// ...

其实我们可以发现,它们的功能都是给 Vue 的 prototype 上扩展一些方法,比如 _init$set$delete$watch(这里皆不作详细介绍),Vue 按功能把这些扩展分散到多个模块中去实现,这种方式是用 Class 难以实现的。

Vue 本身是一个函数,然后用函数实现的类,我们可以理解为它 实际上就是一个类,然后该类上挂载了很多原型的方法,其实啊,不止是这里挂载了原型方法,在前面看到的几个文件中也都有类似操作。

initGlobalAPI(Vue)

我们回到 initGlobalAPI(Vue) ,Vue.js 在整个初始化过程中,除了给它的原型 prototype 上扩展方法,还会给 Vue 这个对象本身扩展全局的静态方法,它的定义在 src/core/global-api/index.js中。

// ...
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 的 config 上定义了 configDef,打开 '../config' ,这里面定义了很多 Vue 的全局属性。除此之外,还定义了一个 util 方法,而官方是不推荐使用的,因为它不够稳定,可能随时会改变。

我们继续往下看,发现了这行代码

extend(Vue.options.components, builtInComponents)

builtInComponents 是定义的一个内置组件,返回了一个 KeepAlive,通过 extend 方法,把它扩展到 Vue.options.components 中。接下来还定义了几个全局的方法,所以我们平时使用的一些方法都不是凭空而来,都是在初始化过程中去定义过的。

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

总结

到这里我们大致知道了,在 Vue 初始化过程中,基本可以分为两个过程,第一个是 Vue 的最终定义,也就是在 './instance/index' 中,我们了解到通过 xxxMixin 方法,在 Vue 的原型上挂载了很多方法。第二个就是通过 initGlobalAPI,给 Vue 挂载了很多静态方法。 这样之后,我们就可以在代码中去使用 Vue 以及它的一些 API 了。 下一章,我们将会迎来重点,Vue 的核心实现,敬请关注。