概念
当我们执行 new Vue()开始到被创建完成,vue需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
beforeCreate
问题:beforeCreate为vue初始化过程中第一个钩子函数,那么从new Vue()到beforeCreate过程它做了什么?
从图中我们可以到,这个过程中,进行了初始化事件、生命周期。
我们从vue2源码上看看
1、vue-dev\src\core\instance\index.js
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)
}
//初始化mixin
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
我们找到vue入口文件,可以发现vue函数中执行了_init(options)初始化方法,但是这个方法并没有在当前文件定义。函数的外面执行了initMixin(Vue),我们可以得出,这是一个初始化的mixin。
2.\vue-dev\src\core\instance\init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 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
)
}
// 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')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
我们可以看到,在执行beforeCreate之前,做了几件事:初始化生命周期initLifecycle,初始化事件initEvents,初始化RenderinitRender。
3.初始化生命周期initLifecycle
路径: \vue-dev\src\core\instance\lifecycle.js
export function initLifecycle (vm: Component) {
// 获取new vue传入的参数对象
const options = vm.$options
// 定位vm选项第一个非抽象父级,没有的话为undefined
let parent = options.parent
//如果当前实例有父实例并且当前实例abstract为false
if (parent && !options.abstract) {
// 当父实例的parent是抽象组件,向上查找,直到找到最顶级不是抽象的父实例
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// parent第一个非抽象parent,添加vm实例为子元素
parent.$children.push(vm)
}
//vue实例定义$parent、$root、$children、$refs属性及其他属性
// 指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中
vm.$parent = parent
// 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己
vm.$root = parent ? parent.$root : vm
// 当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。
vm.$children = []
// 一个对象,持有已注册过 ref 的所有子组件
vm.$refs = {}
// 组件实例相应的 watcher 实例对象
vm._watcher = null
// 表示keep-alive中组件状态,如被激活,该值为false,反之为true
vm._inactive = null
// 也是表示keep-alive中组件状态的属性。
vm._directInactive = false
// 当前实例是否完成挂载(对应生命周期图示中的mounted)
vm._isMounted = false
// 当前实例是否已经被销毁(对应生命周期图示中的destroyed)
vm._isDestroyed = false
// 当前实例是否正在被销毁,还没有销毁完成(介于生命周期图示中deforeDestroy和destroyed之间)
vm._isBeingDestroyed = false
}
我们可以看到,初始化生命周期函数中,vue判断当前组件是否存在父组件,如果存在,循环找到第一个非抽象父组件,并将vm push进去作为子元素。并且定义了$parent、$root、$children、$refs属性及其他属性。
注:
抽象组件:
就像官网说的一样,它在代码中存在,但是自身不会渲染一个 DOM 元素,也不会出现在父组件链中,不会体现在DOM树上。则非抽象组件正好相反。
定义的属性说明见代码块注释
4.初始化事件initEvents
路径:\vue-dev\src\core\instance\events.js
export function initEvents (vm: Component) {
...
}
...省略
Vue.prototype.$once = function(){...}
Vue.prototype.$off= function(){...}
Vue.prototype.$emit = function(){...}
Vue.prototype.$on= function(){...}
我们可以看到,初始化事件其实是在Vue的原型上定义$once、$off、$emit、$on函数
5.初始化Render initRender
路径:\vue-dev\src\core\instance\render.js
export function initRender (vm: Component) {
...省略
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
...省略
}
我们可以看到,初始化Render其实是封装createElement函数,让我们调用更简单。
所以,在调用beforeCreate()函数前,vue主要做了vm实例一些属性的定义和createElement()方法的封装。