vue实例在被创建之前都要经历一系列的初始化的流程,这里面包括设置数据监听,编译模板,挂载实例到dom,更新dom等。在这整个过程,我们需要在默写时候去进行一些自己的操作,所以vue就在这个过程中运行了一些函数,叫做生命周期钩子函数。
上面的流程图能大概描述vue生命周期的触发过程,结合上一篇做一个自己的vue中new vue的整个过程来看会更清晰。
触发生命周期钩子函数
大概的流程我们已经了解了,都有那些生命周期钩子函数,我们也早已经知道了,但是这些钩子函数是怎么触发的呢?这里我们在源码中可以看到,所有的生命周期函数的调用都是通过callHook方法来触发的,而这个方法定义在src/core/instance/lifecycle中:
export function callHook (vm: Component, hook: string) {// #7573 disable dep collection when invoking lifecycle hookspushTarget()const handlers = vm.$options[hook]if (handlers) {for (let i = 0, j = handlers.length; i < j; i++) {try {handlers[i].call(vm)} catch (e) {handleError(e, vm, `${hook} hook`)}}}if (vm._hasHookEvent) {vm.$emit('hook:' + hook)}popTarget()}
根据传入callHook的字符串hook,去拿到对应的回调函数数组,然后遍历执行。
了解了触发方式,下面就一个一个钩子的去看是在哪里触发的
beforeCreate & created
beforeCreated和created都是在实例化vue的阶段,在_init方法中执行的,定义在src/core/instance/init.js中
Vue.prototype._init = function (options?: Object) {// ...initLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')// ...}
两个生命周期函数触发在initState的前后,而这个方法就是用来初始化props,data,methods
,watch, computed等属性,所以在beforeCreate中是不能获取到props,data等定义的数据的。
而且这两个钩子函数触发的时候,还没有渲染dom,所以在这两个钩子中,也是无法访问到dom的。根据这些特性,我们可以很清楚的知道在这两个钩子函数中适合做些什么。
beforeMount & mounted
beforeMount发生在dom挂载之前,她的调用实在mountComponent函数中,定义在src/core/instace/lifecycle.js中
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._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) {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 hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm}
vm._render()函数用来渲染VNode,在这之前执行beforeMount,之后执行vm._update()把VNode渲染到真实dom上,然后执行mounted钩子函数。
这里有一个区别,如果vm.$node是null,表明不是一次组件的初始化,而是外部new Vue初始化过程,那对于组件,它的mounted在哪里触发呢?
在渲染倒真是的dom之后,会执行invokeInsertHook函数,在这个函数里面会把保存的钩子函数依次执行一遍,它定义在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这个钩子函数,对于组件来说,insert定义在src/core/vdom/create-component.js中的componentVNodeHooks中
const componentVNodeHooks = {// ...insert (vnode: MountedComponentVNode) {const { context, componentInstance } = vnodeif (!componentInstance._isMounted) {componentInstance._isMounted = truecallHook(componentInstance, 'mounted')}// ...},}
每个子组件都是在这个钩子函数中执行mounted的,另外invokeInsertHook的添加顺序是先子后父,所有mounted的执行顺序是先父后子。
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 () {if (vm._isMounted) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)// ...}
这里有个判断,只有组件已经mounted之后,才会调用这个钩子函数
updated的执行实在flushSchedulerQueue函数调用的时候,定义在src/core/observer/scheduler.js中
function flushSchedulerQueue () {// ...// 获取到 updatedQueuecallUpdatedHooks(updatedQueue)}function callUpdatedHooks (queue) {let i = queue.lengthwhile (i--) {const watcher = queue[i]const vm = watcher.vmif (vm._watcher === watcher && vm._isMounted) {callHook(vm, 'updated')}}}
callUpdatedHooks函数中,对Watcher数组进行遍历,满足了当前Watcher为vm._watcher以及mounted以及触发这两个条件,就会触发updated函数。
