这里主要记录在平常中对知识的学习,经过结合笔记与自身理解的方式尝试写下总结
文章对细节可能不会一一介绍解释,内容仅做参考
复制代码
这些天在尝试开始对Vue源码的解读,一点一点去了解框架的设计以及实现思路。今天在编码时候想了有关生命周期的问题,恰好晚上就看到了相关知识。做为其中一小步记录一下javascript
每一个Vue实例在被建立以前都要通过一系列的初始化过程。例如设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等
在这个过程当中会执行相应的生命周期钩子函数,给予用户机会在一些特定的场景下添加本身的逻辑代码vue
直接贴上官方生命周期图:java
能够看出生命周期是描述了一个Vue实例在建立、挂载、注销、更新的一个流程在源码中最终执行生命周期的函数是callHook方法和invokeWithErrorHandling方法,它的定义在src/core/instance/lifecycle和src/core/util/error中能够看到:node
// src/core/instance/lifecycle
export function callHook (vm: Component, hook: string) {
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
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()
}
// src/core/util/error
export function invokeWithErrorHandling ( handler: Function, context: any, args: null | any[], vm: any, info: string ) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
复制代码
callHook接收的两个参数分别为Vue实例和要触发的生命周期钩子名后端
在触发时api
查看Vue官网,很容易能够获得有以下钩子:数组
至于他们都有什么做用,官网已经写得很详细,建议看一下:cn.vuejs.org/v2/api/app
除七、八、11外,其余的在平时开发中较经常使用到。它们的执行顺序跟排列顺序同样框架
beforeCreate和created函数都是在实例化Vue的阶段,在_init方法中执行的,它的定义在src/core/instance/init中:dom
Vue.prototype._init = function (options?: Object) {
...
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')
...
复制代码
能够看到beforeCreate和created的钩子调用是在initState函数的先后,initState的做用是初始化props、data、methods、watch、computed等属性
那么显然beforeCreate的钩子函数中就不能获取到props、data中定义的值,也不能调用methods中定义的函数,而created能够
在这俩个钩子函数执行的时候,尚未渲染 DOM,所均访问不到DOM
通常来讲,若是组件在加载的时候须要和后端有交互,放在这俩个钩子函数执行均可以,若是是须要访问props、data等数据的话,就须要使用created钩子函数
beforeMount和mounted函数执行在Vue实例挂载阶段,它们的调用时机是在mountComponent函数中,定义在src/core/instance/lifecycle:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
vm.$el = el
...
callHook(vm, 'beforeMount')
...
// 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
}
复制代码
在执行vm._render()函数渲染 VNode 以前,执行了beforeMount钩子函数,在执行完vm._update()把 VNode patch 到真实 DOM 后,执行mounted钩子
注意,这里对mounted钩子函数执行有一个判断逻辑,vm.$vnode若是为null,则代表这不是一次组件的初始化过程,而是咱们经过外部new Vue初始化过程
而那么对于组件,它的mounted时机在哪儿呢
经过阅读源码咱们能够发现(伪装你们都阅读源码,由于上下文只对生命周期进行总结,因此深刻的就不说啦~),在组件VNode patch到DOM后,会执行invokeInsertHook函数(定义在src/core/vdom/patch.js),会把insertedVnodeQueue里面保存的全部mounted钩子函数依次执行一遍
这一些都是题外话了,先记住Vue组件在实例化的时候会先等待子组件的实例化完成,因此insertedVnodeQueue(保存组件的mounted钩子函数的数组)的添加顺序是先子后父
因此对于同步渲染的组件而言,mounted钩子函数的执行顺序是先子后父
beforeUpdate和updated的钩子函数执行时机都是在数据更新的时候
beforeUpdate的执行时机是在 渲染Watcher 的before函数中,在mountComponent函数中能够看到(src/core/instance/lifecycle):
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component {
vm.$el = el
...
callHook(vm, 'beforeMount')
let updateComponent
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
return vm
}
复制代码
这里有个判断,也就是在组件已经mounted以后才会去调用这个钩子函数
update的执行时机是在flushSchedulerQueue函数调用的时候, 它的定义在src/core/observer/scheduler中:
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
...
callUpdatedHooks(updatedQueue)
}
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
}
复制代码
至于深刻,这里就不解释啦~ (还不会
beforeDestroy和destroyed钩子函数的执行时机在组件销毁的阶段
beforeDestroy钩子函数的执行时机是在destroy函数执行最开始的地方,接着执行了一系列的销毁动做,包括从parent的children中删掉自身,删除watcher,当前渲染的VNode执行销毁钩子函数等,执行完毕后再调用destroy钩子函数 在$destroy的执行过程当中,它又会执行vm.patch(vm._vnode, null)触发它子组件的销毁钩子函数,这样一层层的递归调用
因此destroy钩子函数执行顺序是先子后父,和mounted过程同样
activated是keep-alive组件激活时调用
deactivated是keep-alive组件停用时调用
经过对整个生命周期的了解,就能够很清晰地知道能够在什么阶段作什么事,或者某一操做应该在什么阶段执行
例如在create中进行数据操做,在mounted中进行DOM完成后的操做,在destroyed进行事件解绑和功能注销
文章篇幅有点多,不少都是一些相关的衍生,只有在进行源码解读的时候才比较容易理解。在这里最重要的是知道每一个生命周期钩子的时机和做用,其余都是浮云~