生命周期大家肯定都是有所解的,所以在此处就不在过多赘述。
首先来看一下Vue源码的 _init 函数都干了些什么
Vue.prototype._init = function (options?: Object) {
// ...
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之前会初始化事件和生命周期钩子。事件指的是父组件绑定到该组件身上的事件。在此处没有初始化数据,所以一般不进行发送请求的操作。
其次在created之前,这个时候会初始化props,computed,data,methods,watch,这个过程数据已经变为了响应式数据。此处可以进行异步请求,但是修改数据并不会触发beforeUpdate。显然,beforeCreate的钩子函数当中就不能获取到props,data当中的值,也不能调用methods当中的方法。
接着是beforeMount 和 mounted
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// ...
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
在执行_render渲染vnode之前,执行beforeMount,执行完成后把vnode映射到真实dom执行mounted钩子,如果为null表示这是通过new Vue初始化的过程,不为null表示组件初始化的过程。
export default class Watcher {
// ...省略
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
在watcher构造器当中,判断回调函数cb是否是一个函数,如果是函数赋值给this.getter() 然后调用this.get方法
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
首先将这个watcher实例入栈,设置Dep.target = watcher实例,也就是启用依赖收集,然后调用this.getter(),也就是updateComponent这个回调函数。执行_render把vnode映射到真实dom,在这个方法中会访问到所依赖的数据,触发getter,然后判断Dep.target是否存在,我们在pushTarget中已经启用了依赖收集,所以这个时候就会通过判断,执行depend方法,调用Watcher的addDep方法,在addDep方法中,首先获取dep的id,然后判断newDepIds数组中是否存在这个id,防止重复收集依赖。
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
// Dep
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
到这里,依赖收集就已经结束了。当数据更新之后会执行beforeUpdate,然后使用diff进行虚拟dom的对比(这次的vnode与上次的vnode),然后重新渲染,当更新完成后,执行updated钩子,数据已经更新,dom也重新render完成,此时可以操作更新之后的vnode了。