Skip to content

Latest commit

 

History

History
879 lines (699 loc) · 32.9 KB

vue源码.md

File metadata and controls

879 lines (699 loc) · 32.9 KB

Vue 源码阅读

"name": "vue"
"version": "2.6.10"
├── scripts ------------------------------- 构建相关的文件,一般情况下我们不需要动
│   ├── git-hooks ------------------------- 存放git钩子的目录
│   ├── alias.js -------------------------- 别名配置
│   ├── config.js ------------------------- 生成rollup配置的文件
│   ├── build.js -------------------------- 对 config.js 中所有的rollup配置进行构建
│   ├── ci.sh ----------------------------- 持续集成运行的脚本
│   ├── release.sh ------------------------ 用于自动发布新版本的脚本
├── dist ---------------------------------- 构建后文件的输出目录
├── examples ------------------------------ 存放一些使用Vue开发的应用案例
├── flow ---------------------------------- 类型声明,使用开源项目 [Flow](https://flowtype.org/)
├── packages ------------------------------ 存放独立发布的包的目录
├── test ---------------------------------- 包含所有测试文件
├── src ----------------------------------- 这个是我们最应该关注的目录,包含了源码
│   ├── compiler -------------------------- 编译器代码的存放目录,将 template 编译为 render 函数
│   ├── core ------------------------------ 存放通用的,与平台无关的代码
│   │   ├── observer ---------------------- 响应系统,包含数据观测的核心代码
│   │   ├── vdom -------------------------- 包含虚拟DOM创建(creation)和打补丁(patching)的代码
│   │   ├── instance ---------------------- 包含Vue构造函数设计相关的代码
│   │   ├── global-api -------------------- 包含给Vue构造函数挂载全局方法(静态方法)或属性的代码
│   │   ├── components -------------------- 包含抽象出来的通用组件
│   ├── server ---------------------------- 包含服务端渲染(server-side rendering)的相关代码
│   ├── platforms ------------------------- 包含平台特有的相关代码,不同平台的不同构建的入口文件也在这里
│   │   ├── web --------------------------- web平台
│   │   │   ├── entry-runtime.js ---------- 运行时构建的入口,不包含模板(template)到render函数的编译器,所以不支持 `template` 选项,我们使用vue默认导出的就是这个运行时的版本。大家使用的时候要注意
│   │   │   ├── entry-runtime-with-compiler.js -- 独立构建版本的入口,它在 entry-runtime 的基础上添加了模板(template)到render函数的编译器
│   │   │   ├── entry-compiler.js --------- vue-template-compiler 包的入口文件
│   │   │   ├── entry-server-renderer.js -- vue-server-renderer 包的入口文件
│   │   │   ├── entry-server-basic-renderer.js -- 输出 packages/vue-server-renderer/basic.js 文件
│   │   ├── weex -------------------------- 混合应用
│   ├── sfc ------------------------------- 包含单文件组件(.vue文件)的解析逻辑,用于vue-template-compiler包
│   ├── shared ---------------------------- 包含整个代码库通用的代码
├── package.json -------------------------- 不解释
├── yarn.lock ----------------------------- yarn 锁定文件
├── .editorconfig ------------------------- 针对编辑器的编码风格配置文件
├── .flowconfig --------------------------- flow 的配置文件
├── .babelrc ------------------------------ babel 配置文件
├── .eslintrc ----------------------------- eslint 配置文件
├── .eslintignore ------------------------- eslint 忽略配置
├── .gitignore ---------------------------- git 忽略配置

// package.json
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",

mainmodule 指向的都是运行时版的Vue,不同的是:前者是 cjs 模块,后者是 es 模块。

如果按照输出的模块形式分类,那么 Vue 有三种不同的构建输出,分别是:cjses 以及 umd,打开 scripts/config.js 文件,如下图:

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.dev.js'),
    format: 'cjs',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime only ES modules build (for bundlers)
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler ES modules build (for bundlers)
  'web-full-esm': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.js'),
    format: 'es',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  ……	
}

每种模块形式又分别输出了 运行时版 以及 完整版

运行时的入口文件名字为:entry-runtime.js

完整版的入口文件名字为:entry-runtime-with-compiler.js

运行时版 + Compiler = 完整版完整版运行时版多了一个 Compiler,一个将字符串模板(template)编译为 render 函数的东西。大家想一想:将 template 编译为 render 函数的这个过程,是不是一定要在代码运行的时候再去做?当然不是,实际上这个过程在构建的时候就可以完成,这样真正运行的代码就免去了这样一个步骤,提升了性能。同时,将 Compiler 抽离为单独的包,还减小了库的体积。

那么为什么需要完整版呢?说白了就是允许你在代码运行的时候去现场编译模板,在不配合构建工具的情况下可以直接使用,但是更多的时候推荐你配合构建工具使用运行时版本。

为什么还要输出不同形式的模块?比如 cjsesumd?其中 umd 是使得你可以直接使用 <script> 标签引用 Vue 的模块形式。但我们使用 Vue 的时候更多的是结合构建工具,比如 webpack之类的,而 cjs 形式的模块就是为 browserifywebpack 1 提供的,他们在加载模块的时候不能直接加载 ES Module。而 webpack2+ 以及 Rollup 是可以直接加载 ES Module 的,所以就有了 es 形式的模块输出。

Vue 构造函数

我们以 entry-runtime-with-compiler.js 入口文件为切入点, 找到并探究 Vue 构造函数。

回顾 scripts/config.js ,可以发现入口文件是在 web 目录下的,那 web 目录到底指向哪里呢?

打开根目录下的 scripts/alias.js 文件:

module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  entries: resolve('src/entries'),
  sfc: resolve('src/sfc')
}

原来 web 指向的应该是 src/platforms/web。除了 web 之外,alias.js 文件中还配置了其他的别名。

查找 Vue 构造函数:

// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'

// src/platforms/web/runtime/index.js
import Vue from 'core/index'

// src/core/index.js
import Vue from './instance/index'

终于在 src/core/instance/index.js 下找到了!

Vue 构造函数的原型

// 从五个文件导入五个方法(不包括 warn)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// 定义 Vue 构造函数
function Vue (options) {
  // 确保被当做构造函数调用,而不是函数调用
  if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

// 将 Vue 作为参数传递给导入的五个方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

// 导出 Vue
export default Vue

下面依次看下这5个方法,对 Vue 做了什么~

Vue 构造函数中调用的 _init 方法就是在这里定义的,并且定义在 Vue 原型对象上。

let uid = 0
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
      
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // 优化内部组件实例化
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      // 由于动态选项合并非常慢,而且内部组件选项都不需要特殊处理
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
	/* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件
    initRender(vm) // 初始化渲染
    callHook(vm, 'beforeCreate') // 触发 beforeCreate 回调
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 初始化 props、methods、data、computed、watch
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created') // 触发 created 回调
	
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
      
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

首先声明了常量 vm,其值为 this 也就是当前这个 Vue 实例啦,然后在实例上添加了一个唯一标示:_uid,其值为 uiduid 这个变量定义在 initMixin 方法的上面,初始化为 0,可以看到每次实例化一个 Vue 实例之后,uid 的值都会 ++

let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
}

// 中间的代码省略...

/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
}

接下来这两段代码拥有相同的判断语句:

if (process.env.NODE_ENV !== 'production' && config.performance && mark)

意思是:在非生产环境下,并且 config.performancemark 都为真,那么才执行里面的代码,其中 config.performance 来自于 core/config.js 文件。

Vue 提供了全局配置 Vue.config.performance,我们通过将其设置为 true,即可开启性能追踪,你可以追踪四个场景的性能:

  • 组件初始化(component init)
  • 编译(compile),将模板(template)编译成渲染函数
  • 渲染(render),其实就是渲染函数的性能,或者说渲染函数执行且生成虚拟DOM(vnode)的性能
  • 打补丁(patch),将虚拟DOM渲染为真实DOM的性能

其中组件初始化的性能追踪就是我们在 _init 方法中看到的那样去实现的,其实现的方式就是在初始化的代码的开头和结尾分别使用 mark 函数打上两个标记,然后通过 measure 函数对这两个标记点进行性能计算。markmeasure 这两个函数可以在附录 core/util 目录下的工具方法全解 中查看其作用和实现方式。

再来看看这两段代码中间的代码,也就是被追踪性能的代码:

// a flag to avoid this being observed
vm._isVue = true

首先在 Vue 实例上添加 _isVue 属性,并设置其值为 true。目的是用来标识一个对象是 Vue 实例,即如果发现一个对象拥有 _isVue 属性并且其值为 true,那么就代表该对象是 Vue 实例。这样可以避免该对象被响应系统观测(其实在其他地方也有用到,但是宗旨都是一样的,这个属性就是用来告诉你:我不是普通的对象,我是Vue实例)。

// merge options
if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
} else {
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
}

然后这段代码是一个 if 分支语句,条件是:options && options._isComponent,其中 options 就是我们调用 Vue 时传递的参数选项, options._isComponent 是一个内部选项,在 Vue 创建组件的时候才会用到。

走到 else 分支, 在 Vue 实例上添加了 $options 属性,这个属性用于当前 Vue 的初始化。可以发现,后面这些初始化方法中都用到了实例的 $options 属性,即 vm.$options

mergeOptions 具体做了什么,请看 Vue 选项的规范化与合并

_init 是一个内部初始化方法,尤其关注下半部分对 Vue 实例的初始化:

initLifecycle 初始化 Vue 实例属性

initEvents 初始化事件

initRender 初始化渲染

callHook(vm, 'beforeCreate')

initInjections 初始化 inject

initState 初始化 props、methods、data、computed、watch

initProvide 初始化 provide

callHook(vm, 'created')

可以结合 Vue 生命周期图来看初始化过程

vue-lifecycle-1

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // flow 在使用 Object.defineProperty 直接定义对象的时候会出现莫名其妙的问题
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  // 所以我们必须循序渐进地建立对象
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
      // ...
  }
}

先看中间两句,使用 Object.definePropertyVue.prototype 上定义了两个属性:$data$props,这两个属性的定义分别写在了 dataDef 以及 propsDef 这两个对象里,我们来仔细看一下这两个对象的定义,首先是 get

 const dataDef = {}
 dataDef.get = function () { return this._data }
 const propsDef = {}
 propsDef.get = function () { return this._props }

可以看到,$data 属性实际上代理的是 _data 这个实例属性,而 $props 代理的是 _props 这个实例属性。然后有一个是否为生产环境的判断,如果不是生产环境的话,就为 $data$props 这两个属性设置一下 set,实际上就是提示你一下:别他娘的想修改我,老子无敌。

也就是说,$data$props 是两个只读属性,所以,现在让你使用 js 实现一个只读的属性,你应该知道要怎么做了。

接下来 stateMixin 又在 Vue.prototype 上定义了三个方法: $set$delete 以及 $watch

export function eventsMixin (Vue: Class<Component>) {
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
	// ...
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
	// ...
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
	// ...
  }

  Vue.prototype.$emit = function (event: string): Component {
	// ...
  }
}

eventsMixinVue.prototype 上定义了四个方法:$on$once$off$emit

export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
	// ...      
  }

  Vue.prototype.$forceUpdate = function () {
	// ...
  }

  Vue.prototype.$destroy = function () {
	// ...
  }
}

lifecycleMixinVue.prototype 上定义了三个方法:_update$forceUpdate$destroy

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
	// ...
  }

  Vue.prototype._render = function (): VNode {
	// ...
  }
}

renderMixin 一开始以 Vue.prototype 为参数调用了 installRenderHelpers 函数:

export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}

以上代码就是 installRenderHelpers 函数的源码,可以发现,这个函数的作用就是在 Vue.prototype 上添加一系列方法。

renderMixin 方法在执行完 installRenderHelpers 函数之后,又在 Vue.prototype 上添加了两个方法,分别是:$nextTick_render

至此,instance/index.js 文件中的代码就运行完毕了(注意:所谓的运行,是指执行 npm run dev 命令时构建的运行)。我们大概了解了每个 *Mixin 方法的作用其实就是包装 Vue.prototype,在其上挂载一些属性和方法。

下面我们要做一件很重要的事情,就是将上面的内容集中合并起来,放到一个单独的地方,便于以后查看,我将它们整理到了这里:附录/Vue 构造函数整理-原型 ,这样当我们在后面详细讲解的时候,提到某个方法就可以迅速定位它的位置,以便于保持我们思路的清晰。

Vue 构造函数的静态属性和方法(全局API)

core/instance/index.js 文件已经看完了,按照之前我们寻找 Vue 构造函数时的文件路径回溯,下一个我们要看的文件应该就是 core/index.js 文件:

// 从 Vue 的出生文件导入 Vue
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'

// 将 Vue 构造函数作为参数,传递给 initGlobalAPI 方法,该方法来自 ./global-api/index.js 文件
initGlobalAPI(Vue)

// 在 Vue.prototype 上添加 $isServer 属性,该属性代理了来自 core/util/env.js 文件的 isServerRendering 方法
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

// 在 Vue.prototype 上添加 $ssrContext 属性
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 存储了当前 Vue 的版本号
Vue.version = '__VERSION__'

// 导出 Vue
export default Vue

最下面,在 Vue 构造函数上添加了一个静态属性 version,存储了当前 Vue 的版本值,但是这里的 '__VERSION__' 是什么鬼?打开 scripts/config.js 文件,找到 genConfig 方法,其中有这么一句话:__VERSION__: version。这句话被写在了 rollupreplace 插件中,也就是说,__VERSION__ 最终将被 version 的值替换,而 version 的值就是 Vue 的版本号。

回过头来看看 initGlobalAPI(Vue) ,看上去像是在 Vue 上添加一些全局的 API,实际上就是这样的,这些全局 API 以静态属性和方法的形式被添加到 Vue 构造函数上。打开 src/core/global-api/index.js 文件找到 initGlobalAPI 方法:

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

首先,是在 Vue 构造函数上添加 config 属性,这个属性的添加方式类似我们前面看过的 $data 以及 $props,也是一个只读的属性,并且当你试图设置其值时,在非生产环境下会给你一个友好的提示。

// 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 上添加了 util 属性,这是一个对象,这个对象拥有四个属性分别是:warnextendmergeOptions 以及 defineReactive。这四个属性来自于 core/util/index.js 文件。

这里有一段注释,大概意思是 Vue.util 以及 util 下的四个方法都不被认为是公共 API 的一部分,要避免依赖他们,但是你依然可以使用,只不过风险你要自己控制。并且,在官方文档上也并没有介绍这个全局 API,所以能不用尽量不要用。

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.options = Object.create(null)

然后,是在 Vue 上添加了五个属性分别是 setdeletenextTickobservable 以及 options,这里要注意的是 Vue.options,现在它还只是一个空的对象,通过 Object.create(null) 创建。

不过接下来,Vue.options 就不是一个空的对象了,因为下面这段代码:

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)

ASSET_TYPES 来自于 shared/constants.js 文件,打开这个文件,发现 ASSET_TYPES 是一个数组:

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

紧接着,是这句代码:

extend(Vue.options.components, builtInComponents)

extend 来自于 shared/util.js 文件,可以在 附录/shared/util.js 文件工具方法全解 中查看其作用,总之这句代码的意思就是将 builtInComponents 的属性混合到 Vue.options.components 中,其中 builtInComponents 来自于 core/components/index.js 文件。

执行上面的代码后, Vue.options 变成了这样:

Vue.options = {
	components: {
		KeepAlive
	},
	directives: Object.create(null),
	filters: Object.create(null),
	_base: Vue
}

最后部分,以 Vue 为参数调用了四个 init* 方法:

initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    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
  }
}

initUse 方法的作用是在 Vue 构造函数上添加 use 方法,也就是传说中的 Vue.use 这个全局 API,这个方法大家应该不会陌生,用来安装 Vue 插件。

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

initMixin 方法的作用是在 Vue 上添加 mixin 这个全局 API。

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
   */
  Vue.extend = function (extendOptions: Object): Function {
	// ...
  }
}

initExtend 方法在 Vue 上添加了 Vue.cid 静态属性,和 Vue.extend 静态方法。

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
	// ...
  })
}

所以,经过 initAssetRegisters 方法,Vue 又多了三个静态方法:

Vue.component
Vue.directive
Vue.filter

这样,initGlobalAPI 方法的全部功能我们就介绍完毕了,它的作用就像它的名字一样,是在 Vue 构造函数上添加全局的API,类似整理 Vue.prototype 上的属性和方法一样,我们同样对 Vue 静态属性和方法做一个整理,将它放到 附录/Vue 构造函数整理-全局API 中,便于以后查阅。

至此,对于 core/index.js 文件的作用我们也大概清楚了,在这个文件里,它首先将核心的 Vue,也就是在 core/instance/index.js 文件中的 Vue,也可以说是原型被包装(添加属性和方法)后的 Vue 导入,然后使用 initGlobalAPI 方法给 Vue 添加静态方法和属性,除此之外,在这个文件里,也对原型进行了修改,为其添加了两个属性:$isServer$ssrContext,最后添加了 Vue.version 属性并导出了 Vue

Vue 平台化的包装

现在,在我们弄清 Vue 构造函数的过程中已经看了两个主要的文件,分别是:core/instance/index.js 文件以及 core/index.js 文件,前者是 Vue 构造函数的定义文件,我们一直都叫其 Vue 的出生文件,主要作用是定义 Vue 构造函数,并对其原型添加属性和方法,即实例属性和实例方法。后者的主要作用是,为 Vue 添加全局的API,也就是静态的方法和属性。这两个文件有个共同点,就是它们都在 core 目录下,我们在介绍 Vue 项目目录结构的时候说过:core 目录存放的是与平台无关的代码,所以无论是 core/instance/index.js 文件还是 core/index.js 文件,它们都在包装核心的 Vue,且这些包装是与平台无关的。但是,Vue 是一个 Multi-platform 的项目(web 和 weex),不同平台可能会内置不同的组件、指令,或者一些平台特有的功能等等,那么这就需要对 Vue 根据不同的平台进行平台化地包装,这就是接下来我们要看的文件,这个文件也出现在我们寻找 Vue 构造函数的路线上,它就是:platforms/web/runtime/index.js 文件。下面来看一看里面的代码:

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

设置平台化的 Vue.config,覆盖默认导出的 config 对象的属性,安装平台特定的工具方法。

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

Vue.options 上混合了两个指令(directives),分别是 modelshow

Vue.options 上混合了两个组件(components),分别是 TransitionTransitionGroup

Vue.options 上添加 web 平台运行时的特定组件和指令后,Vue.options 将变为:

Vue.options = {
	components: {
		KeepAlive,
		Transition,
		TransitionGroup
	},
	directives: {
		model,
		show
	},
	filters: Object.create(null),
	_base: Vue
}
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

Vue.prototype 上添加了两个方法:__patch__$mount$mount 在 _init 方法中用到了。

再往下的一段代码是 vue-devtools 的全局钩子,它被包裹在 setTimeout 中,最后导出了 Vue

with compiler

在看完 runtime/index.js 文件之后,其实 运行时 版本的 Vue 构造函数就已经“成型了”。我们可以打开 entry-runtime.js 这个入口文件,这个文件只有两行代码:

import Vue from './runtime/index'

export default Vue

可以发现,运行时 版的入口文件,导出的 Vue 就到 ./runtime/index.js 文件为止。然而我们所选择的并不仅仅是运行时版,而是完整版的 Vue,入口文件是 entry-runtime-with-compiler.js,我们知道完整版和运行时版的区别就在于 compiler,所以其实在我们看这个文件的代码之前也能够知道这个文件的作用:就是在运行时版的基础上添加 compiler,对没错,这个文件就是干这个的,接下来我们就看看它是怎么做的,打开 entry-runtime-with-compiler.js 文件:

// ... 其他 import 语句

// 导入运行时的 Vue
import Vue from './runtime/index'

// ... 其他 import 语句

// 从 ./compiler/index.js 文件导入 compileToFunctions
import { compileToFunctions } from './compiler/index'

// 根据 id 获取元素的 innerHTML
const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

// 使用 mount 变量缓存 Vue.prototype.$mount 方法
const mount = Vue.prototype.$mount
// 重写 Vue.prototype.$mount 方法
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
	// ... 
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}
// 在 Vue 上添加一个全局API `Vue.compile` 其值为上面导入进来的 compileToFunctions
Vue.compile = compileToFunctions

export default Vue