Vue作为当下最流行的渐进式的js框架,其渐进式的思想、虚拟DOM的运用、组件化的开发模式、响应式数据侦听原理值得开发者进行探索学习,其中运用的代码组织的技巧,对平时的开发大有裨益。本系列笔记初步分为8个章节,记录本人源码的阅读过程。
虽然Vue源码打包压缩后只有20kb,足够小巧轻量,但是源码还是相对复杂,开发者为了增强源码的可读性和可维护性,在ES2015和ESLint基础上,引入Flow做静态类型检查。Flow和TypeScript
极为相似,前者由Facebook出品,后者则是微软的杰作。
Flow的类型检查分2种方式:
具体的语法和细节可以阅读官方文档:flow.org
Vue.js 源码是基于 Rollup 构建的,Rollup和webpack类似,但是Rollup相比webpack更加轻量,没有对图片等文件的打包处理,适合js框架的打包,构建相关配置在scripts目录下。最终的打包版本分为 Runtime Only
和 Runtime + Compiler
,显然Runtime Only
版本会更加轻量,需要webpack在开发环境对vue文件预编译为js文件,由于前端浏览器进行template编译会消耗大量性能,因此开发更加推荐Runtime Only
方式,此处学习过程参考的是 Runtime + Compiler
。
Vue.js 的源码存放在 src 目录下,整个目录结构十分清晰,目录结构如下所示:
src
├── compiler # 编译
├── core # 核心代码
├── platforms # 平台支持,web & weex
├── server # 服务端渲染
├── sfc # vue文件解析
├── shared # 共享代码
从配置文件夹script下的config.js可以看到,入口文件为平台目录下的 src/platforms/web/entry-runtime.js,在入口文件中能够找到引用了 src/core/index.js 下的Vue,最终确定Vue的定义在 src/core/instance/index.js
'web-runtime-cjs': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.js'),
format: 'cjs',
banner
}
Vue的定义非常清晰,Vue是一个 Function 类,只能通过 new Vue
实例化。Vue的定义下面有多个Mixin
方法,可以给 Vue 的 prototype 扩展方法,这种开发技巧能够使得代码结构更加清晰、易于维护,但这种方式ES6的Class难以实现,因此此处使用的是ES5的Funtion构建类
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'
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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Vue类使用 Function 实现,只能通过 new 关键字初始化,在上面的Vue定义中可以看到,Vue的初始化主要是通过 this._init
方法,传入options
参数,_init
方法在 src/core/instance/init.js 中定义。Vue 初始化进行了合并配置、初始化生命周期、初始化事件、初始化渲染、初始化 data/props/computed/watcher 等,都是通过调用函数执行,这些函数分别封装在不同的js文件中。初始化最后,检测到如果有 el
属性,则调用 vm.$mount
方法挂载 vm
,即把模板渲染成最终的 DOM。
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')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, '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)
}
}
}