callHook
执行生命周期的函数都是调用callHook方法
定义在src/core/instance/lifecycle中
export function callHook (vm: Component, hook: string) {// #7573 disable dep collection when invoking lifecycle hookspushTarget()// 根据传入的字符串hook,拿到对应的回调函数数组const handlers = vm.$options[hook]const info = `${hook} hook`if (handlers) {// 遍历执行且把vm作为函数执行的上下文for (let i = 0, j = handlers.length; i < j; i++) {invokeWithErrorHandling(handlers[i], vm, null, vm, info)}}if (vm._hasHookEvent) {vm.$emit('hook:' + hook)}popTarget()}
callhook函数功能就是调用某个生命周期钩子注册的所有回调函数
beforeCreate & created
在实例化Vue阶段,在_init方法中执行
定义在src/core/instance/init.js中
Vue.prototype._init = function (options?: Object) {// ...vm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate') // 获取不到props、data中定义的值,也不能调用methods中定义的函数initInjections(vm) // resolve injections before data/propsinitState(vm) // 初始化props、data、methods、watch、computedinitProvide(vm) // resolve provide after data/propscallHook(vm, 'created')// ...}
这两个钩子函数执行的时候并没有渲染DOM,所以也不能够访问DOM
一般来说如果组件在加载的时候需要和后端做交互,放在这两个钩子函数执行都可以
如果要访问props、data等数据的话就需要使用created钩子函数
vue-router和vuex都混合了beforeCreate钩子函数
beforeMount & mounted
beforeMount钩子函数发生在mount,也就是DOM挂载之前,是在mountComponent函数中调用的
定义在src/core/instance/lifecycle.js中
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean): Component {vm.$el = elif (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm)} else {warn('Failed to mount component: template or render function not defined.',vm)}}}callHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst 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 definednew Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {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// vm.$vnode == null成立表面这不是一次组件的初始化过程,而是通过外部new Vue初始化的过程if (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm}
在执行vm._render()函数渲染VNode之前,执行了beforeMount钩子函数,在执行完vm._update()把VNode patch到真实DOM后执行mounted钩子
组件的mounted时机
组件的VNode patch到DOM后会执行invokeInsertHook函数,把insertedVnodeQueue里保存的钩子函数依次执行一遍
定义在src/core/vdom/patch.js中
function invokeInsertHook (vnode, queue, initial) {// delay insert hooks for component root nodes, invoke them after the// element is really insertedif (isTrue(initial) && isDef(vnode.parent)) {vnode.parent.data.pendingInsert = queue} else {for (let i = 0; i < queue.length; ++i) {queue[i].data.hook.insert(queue[i])}}}
对于组件,insert钩子函数定义在src/core/vdom/create-component.js中
const componentVNodeHooks = {// ...insert (vnode: MountedComponentVNode) {const { context, componentInstance } = vnodeif (!componentInstance._isMounted) {componentInstance._isMounted = true// 子组件执行mounted钩子函数callHook(componentInstance, 'mounted')}if (vnode.data.keepAlive) {if (context._isMounted) {// vue-router#1212// During updates, a kept-alive component's child components may// change, so directly walking the tree here may call activated hooks// on incorrect children. Instead we push them into a queue which will// be processed after the whole patch process ended.queueActivatedComponent(componentInstance)} else {activateChildComponent(componentInstance, true /* direct */)}}},// ...}
对于同步渲染的子组件而言,mounted钩子函数的执行顺序是先子后父
beforeUpdate & updated
在数据更新时执行beforeUpdate和updated钩子函数
beforeUpdate的执行时机是在渲染Watcher的before函数中
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean): Component {// ...// 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 definednew Watcher(vm, updateComponent, noop, {before () {// 组件mounted之后才会调用这个钩子函数if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)// ...}
update执行时机是在flushSchedulerQueue函数调用的时候
定义在src/core/observer/scheduler.js中
/*** Flush both queues and run the watchers.*/function flushSchedulerQueue () {currentFlushTimestamp = getNow()flushing = truelet watcher, id// Sort queue before flush.// This ensures that:// 1. Components are updated from parent to child. (because parent is always// created before the child)// 2. A component's user watchers are run before its render watcher (because// user watchers are created before the render watcher)// 3. If a component is destroyed during a parent component's watcher run,// its watchers can be skipped.queue.sort((a, b) => a.id - b.id)// do not cache length because more watchers might be pushed// as we run existing watchersfor (index = 0; index < queue.length; index++) {watcher = queue[index]if (watcher.before) {watcher.before()}id = watcher.idhas[id] = nullwatcher.run()// in dev build, check and stop circular updates.if (process.env.NODE_ENV !== 'production' && has[id] != null) {circular[id] = (circular[id] || 0) + 1if (circular[id] > MAX_UPDATE_COUNT) {warn('You may have an infinite update loop ' + (watcher.user? `in watcher with expression "${watcher.expression}"`: `in a component render function.`),watcher.vm)break}}}// keep copies of post queues before resetting stateconst activatedQueue = activatedChildren.slice()// 更新了的watcher数组const updatedQueue = queue.slice()resetSchedulerState()// call component updated and activated hookscallActivatedHooks(activatedQueue)callUpdatedHooks(updatedQueue)// devtool hook/* istanbul ignore if */if (devtools && config.devtools) {devtools.emit('flush')}}function callUpdatedHooks (queue) {let i = queue.lengthwhile (i--) {const watcher = queue[i]const vm = watcher.vm// 当前watcher为vm._watcher,且组件已经mountedif (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {callHook(vm, 'updated')}}}
在组建mount的过程中会实例化一个渲染的Watcher去监听vm上的数据变化重新渲染,发生在mountComponent函数执行时
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean): Component {// ...let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst 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 definednew Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)// ...}
判断isRenderWatcher,把当前watcher的实例赋值给vm_watcher
定义在src/core/observer/watcher.js中
export default class Watcher {// ...constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vm// 判断isRenderWatcher,把当前watcher的实例赋值给vm_watcherif (isRenderWatcher) {vm._watcher = this}// vm._watcher专门用来监听vm上数据变化然后重新渲染,与渲染相关的watchervm._watchers.push(this)// ...}}
callUpdatedHooks函数中只有vm._watcher的回调函数执行完毕后才会执行updated钩子函数
beforeDestroy & destroyed
beforeDestroy 和 destroyed 钩子函数的执行时机在组件销毁的阶段,它们最终会调用$destroy方法
定义在src/core/instance/lifecycle.js中
Vue.prototype.$destroy = function () {const vm: Component = thisif (vm._isBeingDestroyed) {return}callHook(vm, 'beforeDestroy')vm._isBeingDestroyed = true// remove self from parent// 从parent的$.children中删除自身const parent = vm.$parentif (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm)}// teardown watchers// 删除watcherif (vm._watcher) {vm._watcher.teardown()}let i = vm._watchers.lengthwhile (i--) {vm._watchers[i].teardown()}// remove reference from data ob// frozen object may not have observer.if (vm._data.__ob__) {vm._data.__ob__.vmCount--}// call the last hook...vm._isDestroyed = true// invoke destroy hooks on current rendered tree// 当前渲染的VNode执行销毁钩子函数vm.__patch__(vm._vnode, null)// fire destroyed hookcallHook(vm, 'destroyed')// turn off all instance listeners.vm.$off()// remove __vue__ referenceif (vm.$el) {vm.$el.__vue__ = null}// release circular reference (#6759)if (vm.$vnode) {vm.$vnode.parent = null}}
vm.patch(vm._vnode, null)触发它子组件的销毁钩子函数,这样一层层的递归调用,所以destroy钩子函数执行顺序是先子后父
activated & deactivated
专门为keep-alive组件定制的钩子,后续介绍
