生命周期不是营销词汇,而是 Vue 2 运行时内部「可观测的执行链路」。掌握其精确时序,意味着能够在正确的时间点干预响应式系统、DOM 渲染与依赖收集,从而避免性能回退与内存泄漏。下文以源码级视角拆解八个阶段,并给出在大型应用中的工程级实践。
一、生命周期在运行时中的定位
Vue 2 将组件视为「可复用的响应式系统实例」。每个实例在内存中依次经历:
- 初始化
- 依赖收集与模板编译
- 首次渲染
- 运行时更新
- 销毁与回收
上述五个宏观阶段被细分为八个可注入钩子,允许开发者在不破坏内部状态机的前提下执行自定义逻辑。
二、八阶段钩子及其底层行为
1.beforeCreate
触发时机:initLifecycle
完成后,initEvents
之前。
此时响应式系统尚未建立,data
、props
、methods
均不可访问。
用途:注入全局插件或运行时诊断。
2.created
触发时机:initState
完成,响应式追踪就绪,但 DOM 仍为空。
用途:
- 发起 SSR 安全的异步数据请求
- 注册非 DOM (WebSocket、定时器)
3.beforeMount
触发时机:render 函数已生成,虚拟 DOM 树就绪,即将执行首次 patch。
用途:在真实 DOM 挂载前修改渲染输出。
4.mounted
触发时机:patch 完成,DOM 已插入文档流,$el
可访问。
用途:
- 执行原生 DOM 操作(第三方图表、滚动容器初始化)
- 触发 nextTick 以获取布局信息
注意:子组件 mounted 先于父组件执行,因为 patch 是自下而上的深度优先遍历。
5.beforeUpdate
触发时机:响应式数据变更导致虚拟 DOM 重新渲染之前。
用途:手动记录滚动位置或表单状态,禁止在此发起网络请求。
6.updated
触发时机:虚拟 DOM diff & patch 完成,DOM 已同步。
用途:执行依赖 DOM 尺寸的副作用。
禁止在此钩子内再次修改响应式数据,否则可能触发无限循环更新。
7.beforeDestroy
触发时机:组件实例开始拆解,侦听器、子组件、指令尚未卸载。
用途:移除全局事件总线监听、终止轮询或长连接。
js
beforeDestroy() {
clearInterval(this.pollTimer);
this.$eventBus.$off('global-event', this.handleEvent);
if (this.chart) {
this.chart.dispose(); // 第三方实例需手动销毁
}
}
确保在 destroyed 之前解除所有外部引用,防止闭包或全局事件导致的游离 DOM 与内存泄漏。
8.destroyed
触发时机:实例、DOM、指令、子组件完全解耦,仅剩空壳 $el
。
用途:在内存分析工具中标记回收点,通知父级缓存池释放引用。
三、首屏加载时的钩子执行序列
访问应用根路由时,根组件与所有同步子组件按以下顺序触发:
- 根 beforeCreate
- 根 created
- 根 beforeMount
- 子树递归 → 各级子组件 beforeCreate → created → beforeMount
- 子树 mounted 自底向上
- 根 mounted
该序列保证父组件可在 mounted 安全地访问子组件 DOM。
四、父子组件更新时序
- 父数据变更:父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
- 子数据变更:子 beforeUpdate → 子 updated(父组件未受影响)
父组件可在 updated 中聚合子组件最新 DOM 状态,而不会被自己无关的更新打断。
五、工程级落地口诀
- 异步数据预拉取:created
- DOM 尺寸测量:mounted + nextTick
- 列表滚动记忆:beforeUpdate
- 全局事件清理:beforeDestroy
牢记「初始化自上而下、更新自下而上、销毁自上而下」的三条时序规则,便能在任何规模的应用中精准插入副作用、避免冗余渲染、防止内存泄漏。