本篇源代码所在路径vue/src/core/instance/
几乎所有JS框架或插件的编写都有一个类似的模式,即向全局输出一个类或者说构造函数,通过创建实例来使用这个类的公开方法,或者使用类的静态全局方法辅助实现功能。相信精通Jquery或编写过Jquery插件的开发者会对这个模式非常熟悉。Vue.js也如出一辙,只是一开始接触这个框架的时候对它所能实现的功能的感叹盖过了它也不过是一个内容较为丰富和精致的大型类的本质。
核心类 Vue的核心类 的构建文件,代码非常简单,就是一串定义构造函数的基础代码:
1 2 3 4 5 6 7 8 9 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所有功能的实现,这只是一个开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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' ... initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)
在类构造文件的头部引入了同目录下5个文件中的混合函数(我认为这里只是为了要表示把一些方法混入到初始类中才统一用了Mixin的后缀,所以不要深究以为这是什么特殊的函数),分别是初始化 initMixin
、状态 stateMixin
、渲染 renderMixin
、事件 eventsMixin
、生命周期 lifecycleMixin
。在文件尾部将这几个函数里包含的具体方法挂载到Vue原始类上。
从各个细化模块,可以看出作者是如何进行逻辑架构分类的。这里又学到了一种模块开发的好方法,将类继承方法按模块独立编写,单独进行挂载实现了可插拔的便利性。
文件最后的经典代码。到此Vue的类构造完成!
就这样完成了么!且慢,来稍微看一下初始化混合函数初步做了些啥:
初始化的过程 下面代码位于vue/src/core/instance/init.js
最先为基础类挂载的方法就是_init()
,这是唯一在类实例化的过程中执行的函数,位于整个函数栈的最底层,其他的功能将在此方法里初步分化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 export function initMixin (Vue: Class<Component> ) { Vue.prototype._init = function (options?: Object ) { const vm: Component = this vm._uid = uid++ let startTag, endTag if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid} ` endTag = `vue-perf-end:${vm._uid} ` mark(startTag) } vm._isVue = true if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } if (process.env.NODE_ENV !== 'production' ) { initProxy(vm) } else { vm._renderProxy = vm } vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate' ) initInjections(vm) initState(vm) initProvide(vm) callHook(vm, 'created' ) 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) } } }
还记得在文件组织里分析的,Component类的的具体定义可参照这个文件 。
初始化函数内容不多,主要做了这么几件事:
整理options配置对象
开始进入Vue实例的生命周期进程,并在生命周期相应阶段初始化实例属性和方法
将初始化好的对象挂载到Dom元素上,继续生命周期的运行
这部分代码已经完整地展示出了将Vue实例对象挂载到DOM元素上并执行渲染的大半程生命周期的进程,在此之后就是视图的交互过程,直到实例对象被销毁。后半段代码清晰地呈现了生命周期中各个功能的初始化顺序,也就是那张著名的生命周期图示的对应代码。
各个生命周期的初始化函数内容比较丰富,决定在另一个文档中做一个单独讨论类初始化函数详情
虽然核心类的定义代码寥寥数行,但是在类初始化的过程中执行了非常多的其他功能的初始化,从这个基础的类的实现去一步步解开每一个更复杂的功能的实现可能会让学习者能逐步深入了解Vue的丰富内容,基于源代码一句句的解释虽然非常冗余,但是希望即便是基础不是特别扎实的同学也能看懂,认识到源码学习不再是大难题。