往期章节
前面两章中,我们跟随 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 的核心实现,敬请关注。