先决条件
- 需要能够熟悉使用 vue ,了解vue属性和方法。
- 带着问题去看源码
- 熟悉ES6或者Typescript语法
- 确定源码的版本,我看的是 2.6.12
问题
- vue在初始化的时候都做了什么事情?
vue的初始化
上个章节我们知道了vue源代码的入口文件src/platforms/web/entry-runtime.js
。
import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser } from 'core/util/index'
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement
} from 'web/util/index'
import { patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// 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)
}
第一行import Vue from 'core/index'
,就引入了vue的主体,并在这个主体上进行扩展。比如,添加一些平台的特定方法 vue.config.mustUseProp
,不过,我们平常并不会用到。
要注意的是,在入口文件给vue绑定了原型方法 $mount
,这个在后面会用到。
核心文件介绍
上面的代码,很多都引入了core文件,我们了解下 core
这个文件夹的每个文件的作用。
core
翻译过来就是 核心 的意思,vue的核心代码都在这个文件夹内。
-
components
定义组件代码 -
global-api
给vue设置全局配置项,全局API方法等,比如Vue.set Vue.nextTick
等 -
instance
创建vue初始化函数,实例方法,实例属性和构建vue的生命周期,这个就很重要了。 -
observer
创建vue中常用的观察者模型,我们的双向数据绑定和一些watch监听都依赖于它 -
util
创建一些工具函数提供给源码使用 -
vdom
创建vue的虚拟DOM -
config.js
常用的配置项给源码使用 -
index.js
core 的入口代码文件
我们看下 core 的入口代码,第一行 import Vue from './instance/index'
还是在引用vue主体,目前还是没有找到Vue主体。
core/index.js
的作用,第一是通过initGlobalAPI
来初始化Vue全局的配置项和全局API(具体请看vue的文档);第二是定义Vue的原型方法 $isServer ,$ssrContext, FunctionalRenderContext
。
Vue主体函数
通过入口文件,我们继续向里面深挖 ,看到这里老铁们,我们找到了Vue的主体了。
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')
}
// 执行 initMixin中定义的原型方法
this._init(options)
}
// 定义初始化函数,实例方法,触发生命周期钩子函数
initMixin(Vue)
// 定义实例属性,$data, $props 和 实例方法 $set(), $delete(), $watch()
stateMixin(Vue)
// 定义实例方法/属性 $on(), $once(), $off() 和 $emit()
eventsMixin(Vue)
// 定义实例方法/生命周期 $forceUpdate(), $destory(),_update()
lifecycleMixin(Vue)
// 定义实例方法/生命周期 $nextTick(),
// 并且定义内部方法 _render()
renderMixin(Vue)
export default Vue
function Vue() {....}
就是构建Vue的主体函数。
这个文件里面有很多方法比如initMixin(), stateMixin(), eventsMixin()
等 都是围绕者主体函数来构建Vue实例方法,内部方法或者添加钩子函数的,不过这都不重要。
在这里的重点是,当我们使用vue的时候 ,它执行了 function Vue() {....}
主体函数里面的 this._init()
方法。
new Vue({
data: {}
})
调用了 this._init(options)
initMixin()
定义了 this._init()
方法。
export function initMixin (Vue: Class) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// 定义实例属性 $options,可以重置一些自定义property属性和方法,比如自定义 created(),methods() 和 data
// vm.constructor 是在 initGlobalAPI 定义的
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// expose real self
vm._self = vm
// 定义实例属性 $root, $parent, $children, $refs
// 并初始化实例的一些内部属性 _watcher,_inactive, _isMounted 等
initLifecycle(vm)
// 初始化事件
initEvents(vm)
// 定义实例属性 $slots, $scopedSlots, $createElement, $attrs, '$listeners
initRender(vm)
// 触发钩子函数 beforeCreate
callHook(vm, 'beforeCreate')
// provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
initInjections(vm) // resolve injections before data/props
// 初始化状态,比如 data, props, methods, computed和watch
initState(vm)
// provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
initProvide(vm) // resolve provide after data/props
// 当数据,方法等必要属性都初始化后,触发created钩子函数
callHook(vm, 'created')
// 将实例挂载到Dome上
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
_init() 函数做了很多事了,初始化事件,添加了很多实例属性,触发钩子函数,初始化数据,对数据做监听,最后把实例通过$mount()
挂载到dome上。$mount()
函数在一开始src/platforms/web/entry-runtime.js
代码文件被定义。
总结:
从找到编译的源代码入口src/platforms/web/entry-runtime.js
,到最后找到 Vue的主体函数。vue初始化的主线都比较清晰,感觉就像拨洋葱,在核心里创建函数主体,然后给函数主体一层一层添加需要的方法和属性。下一节看vue的生命周期是如何形成的。