Vue 生命周期实现分析 (lifecycle.js)
文件概述
lifecycle.js 是 Vue 核心实现中管理组件生命周期的关键文件,它定义了 Vue 实例从创建到销毁的完整生命周期流程。主要功能包括:
- 组件生命周期的初始化
- 组件的挂载与更新机制
- 父子组件关系的建立
- 生命周期钩子的触发机制
- 组件的销毁与资源回收
该文件是理解 Vue 组件生命周期工作原理的核心入口。
全局变量
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false
- activeInstance: 记录当前正在渲染的组件实例,用于建立父子组件关系
- isUpdatingChildComponent: 标识是否正在更新子组件,避免某些检查在更新过程中触发
核心函数分析
1. initLifecycle - 生命周期初始化
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
这个函数在 Vue 实例创建阶段被调用,主要功能:
1) 建立父子组件关系
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
这段代码建立 Vue 组件树的层级关系:
- 首先获取初始父组件引用
options.parent - 检查条件:当前组件不是抽象组件且有父组件
- 关键点:跳过抽象组件层级,寻找第一个非抽象父组件
- 循环向上查找直到找到非抽象父组件或达到顶层
- 抽象组件如
<keep-alive>或<transition>不会形成额外的组件层级
- 将当前组件添加到实际父组件的
$children数组
这种设计使组件树结构更加清晰,避免抽象组件造成不必要的嵌套层级。
2) 初始化实例属性
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
这段代码初始化组件实例的核心属性:
-
组件树引用:
$parent: 指向父组件(可能是实际父组件,非抽象)$root: 指向根组件(有父组件时使用父的根引用,否则自身为根)$children: 初始化为空数组,用于存储子组件实例
-
DOM 引用:
$refs: 初始化为空对象,用于存储模板引用
-
生命周期状态标志:
_watcher: 渲染 watcher 实例,联系数据变化和视图更新_inactive: 用于 keep-alive 组件,标记是否处于非活动状态_directInactive: 直接非活动状态标记_isMounted: 是否已完成 DOM 挂载_isDestroyed: 是否已完成销毁_isBeingDestroyed: 是否正在销毁过程中
这些属性构成了 Vue 组件实例的骨架,在不同生命周期阶段被更新,帮助 Vue 正确执行组件的生命周期流程。
2. lifecycleMixin - 生命周期方法混入
这个函数向 Vue 原型添加了三个核心方法:
_update - 组件更新方法
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// 渲染流程...
if (!prevVnode) {
// 初始渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// 更新引用...
}
_update 是将虚拟 DOM 转换为实际 DOM 的核心方法,详细流程:
-
准备阶段:
- 保存当前的真实 DOM 引用
prevEl和旧的虚拟节点prevVnode - 调用
setActiveInstance(vm)设置当前活动实例,返回恢复函数 - 保存新的虚拟节点
vm._vnode = vnode
- 保存当前的真实 DOM 引用
-
DOM 渲染核心 - 两种路径:
-
首次渲染 (当
prevVnode不存在):vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)- 将挂载点转换为虚拟节点,与新 vnode 对比
- 根据新 vnode 创建真实 DOM 结构
- 返回新的根 DOM 元素
-
更新渲染 (当已有旧虚拟节点):
vm.$el = vm.__patch__(prevVnode, vnode)- 对比新旧虚拟节点树的差异
- 只更新变化的部分,最小化 DOM 操作
- 返回更新后的根 DOM 元素
-
-
清理与引用更新:
- 调用
restoreActiveInstance()恢复先前的活动实例 - 清除旧 DOM 元素上的 Vue 实例引用
prevEl.__vue__ = null - 在新 DOM 元素上设置 Vue 实例引用
vm.$el.__vue__ = vm - 处理高阶组件特殊情况,更新父组件的 DOM 引用
- 调用
这种设计使 Vue 能够高效地将声明式模板转换为精确的 DOM 操作,同时维护组件实例与 DOM 的正确关联。
$forceUpdate - 强制更新方法
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
这个方法允许手动触发组件更新:
- 直接调用渲染 watcher 的 update 方法
- 不检查数据变化,强制执行渲染流程
- 常用于处理一些响应式系统无法自动检测的变化
使用场景示例:
- 处理非响应式数据变化
export default {
data() {
return { user: { name: '张三' } }
},
methods: {
updateUserInfo() {
// 直接添加新属性,Vue 无法检测到这个变化
this.user.age = 25;
// 需要手动触发更新
this.$forceUpdate();
}
}
}
- 处理复杂第三方库
export default {
mounted() {
this.mapInstance = new ExternalMapLibrary('#map');
},
methods: {
updateMapMarkers(newMarkers) {
// 直接修改第三方库内部数据
this.mapInstance.setMarkers(newMarkers);
// Vue 无法检测此变化,需强制更新
this.$forceUpdate();
}
}
}
通常应避免过度依赖 $forceUpdate,优先使用 Vue.set 或重构数据结构以利用 Vue 的响应式系统。
$destroy - 组件销毁方法
Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// 从父组件中移除自身
// 销毁所有 watcher
// 解除引用...
vm._isDestroyed = true
vm.__patch__(vm._vnode, null)
callHook(vm, 'destroyed')
vm.$off()
// 清理引用...
}
$destroy 方法负责彻底清理组件实例,详细流程:
-
防重入保护:
if (vm._isBeingDestroyed) { return }防止组件被重复销毁,确保销毁过程的幂等性
-
生命周期钩子与状态标记:
callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true- 触发
beforeDestroy钩子,允许用户执行自定义清理 - 设置状态标记,防止重复销毁
- 触发
-
从组件树中移除:
const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) }从父组件的
$children数组中移除当前组件实例 -
清理响应式系统:
if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() }- 拆解主渲染 watcher
- 循环拆解所有用户 watchers (computed, watch 等)
- 断开与响应式系统的连接
-
解除数据引用计数:
if (vm._data.__ob__) { vm._data.__ob__.vmCount-- }减少数据观察者的引用计数
-
清理 DOM 与触发钩子:
vm._isDestroyed = true vm.__patch__(vm._vnode, null) callHook(vm, 'destroyed')- 标记完成销毁
- 通过传入
null移除组件的实际 DOM - 触发
destroyed钩子
-
清理事件与引用:
vm.$off() if (vm.$el) { vm.$el.__vue__ = null } if (vm.$vnode) { vm.$vnode.parent = null }- 移除所有事件监听器
- 解除 DOM 元素与 Vue 实例的关联
- 解除 VNode 树的循环引用
这种多层次的清理机制确保了组件资源的完全释放,防止内存泄漏,是长时间运行的单页应用的重要保障。
3. mountComponent - 组件挂载
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 处理渲染函数...
callHook(vm, 'beforeMount')
// 定义更新函数
let updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 创建渲染 watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 触发 mounted 钩子
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent 函数是组件挂载到 DOM 的核心实现,详细流程:
-
挂载准备:
vm.$el = el保存挂载点 DOM 元素引用
// 处理渲染函数缺失的情况 if (!vm.$options.render) { vm.$options.render = createEmptyVNode // 开发环境警告... }确保渲染函数存在,否则使用空节点渲染函数并在开发环境发出警告
callHook(vm, 'beforeMount')触发挂载前钩子,此时可访问实例但尚未渲染
-
定义更新函数:
let updateComponent 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) } }- 创建封装渲染逻辑的函数
- 开发环境版本添加性能测量标记
- 两个核心步骤:
_render()生成 VNode,_update()应用到 DOM
-
创建渲染 Watcher:
new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)- 创建组件的渲染 Watcher
- 通过
before钩子在更新前触发beforeUpdate - 标记为渲染 watcher
- Watcher 构造函数会立即调用
updateComponent,触发首次渲染
-
挂载完成处理:
hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') }- 重置服务端渲染标志
- 检查是否是根组件(
$vnode == null) - 设置挂载完成标志
_isMounted = true - 触发
mounted钩子
这个函数建立了 Vue 响应式系统与 DOM 渲染之间的桥梁,通过 Watcher 机制确保数据变化时视图能自动更新。
4. updateChildComponent - 子组件更新
export function updateChildComponent (
vm: Component,
propsData: ?Object,
listeners: ?Object,
parentVnode: MountedComponentVNode,
renderChildren: ?Array<VNode>
) {
// 更新子组件...
// 判断是否需要强制更新...
// 更新 props
if (propsData && vm.$options.props) {
// props 更新逻辑...
}
// 更新事件监听器
listeners = listeners || emptyObject
const oldListeners = vm.$options._parentListeners
vm.$options._parentListeners = listeners
updateComponentListeners(vm, listeners, oldListeners)
// 解析插槽并强制更新...
}
updateChildComponent 函数负责在父组件更新时同步更新子组件,详细流程:
-
动态插槽检测:
const newScopedSlots = parentVnode.data.scopedSlots const oldScopedSlots = vm.$scopedSlots const hasDynamicScopedSlot = !!( (newScopedSlots && !newScopedSlots.$stable) || (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) || (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) || (!newScopedSlots && vm.$scopedSlots.$key) )复杂的条件检查作用域插槽是否发生动态变化
-
强制更新条件判断:
const needsForceUpdate = !!( renderChildren || // 有新的静态插槽子节点 vm.$options._renderChildren || // 有旧的静态插槽子节点 hasDynamicScopedSlot // 有动态作用域插槽变化 )确定是否需要强制更新子组件
-
更新虚拟节点引用:
vm.$options._parentVnode = parentVnode vm.$vnode = parentVnode if (vm._vnode) { vm._vnode.parent = parentVnode } vm.$options._renderChildren = renderChildren更新各种虚拟节点引用,维护正确的节点树结构
-
更新响应式属性:
vm.$attrs = parentVnode.data.attrs || emptyObject vm.$listeners = listeners || emptyObject更新透传属性和事件监听器,它们是响应式的
-
更新 Props:
if (propsData && vm.$options.props) { toggleObserving(false) const props = vm._props const propKeys = vm.$options._propKeys || [] for (let i = 0; i < propKeys.length; i++) { const key = propKeys[i] const propOptions = vm.$options.props props[key] = validateProp(key, propOptions, propsData, vm) } toggleObserving(true) vm.$options.propsData = propsData }- 临时关闭观察者避免不必要的响应式转换
- 循环处理每个已定义的 prop
- 验证并更新 prop 值
- 重新开启观察者
- 保存原始 props 数据副本
-
更新事件监听器:
listeners = listeners || emptyObject const oldListeners = vm.$options._parentListeners vm.$options._parentListeners = listeners updateComponentListeners(vm, listeners, oldListeners)更新事件监听器,处理新增和移除的事件
-
插槽解析与强制更新:
if (needsForceUpdate) { vm.$slots = resolveSlots(renderChildren, parentVnode.context) vm.$forceUpdate() }- 当满足强制更新条件时解析新的插槽内容
- 调用
$forceUpdate强制子组件重新渲染
这个函数展示了 Vue 精细的组件更新策略,确保子组件能够准确反映父组件的变化,同时通过条件判断避免不必要的更新。
5. callHook - 钩子调用函数
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()
}
callHook 函数是 Vue 生命周期钩子调用的核心实现,详细流程:
-
暂停依赖收集:
// #7573 disable dep collection when invoking lifecycle hooks pushTarget()- 临时关闭响应式系统的依赖收集功能
- 防止钩子函数中对响应式数据的访问被误收集为依赖
- 注释引用了 GitHub Issue #7573,说明这是特定问题的解决方案
-
获取并调用钩子函数:
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) }- 只在
_hasHookEvent标志为 true 时触发事件 - 事件名称格式为
hook:钩子名(如hook:mounted) - 允许父组件通过
@hook:mounted="handler"监听子组件生命周期
- 只在
-
恢复依赖收集:
popTarget()恢复之前的依赖收集状态
这个函数体现了 Vue 生命周期系统的精妙设计:
- 提供统一的钩子调用机制
- 隔离钩子函数与响应式系统
- 处理由 mixins 和继承产生的多钩子情况
- 支持通过事件系统扩展组件通信
- 提供可靠的错误处理保障
6. 激活/停用相关函数
export function activateChildComponent (vm: Component, direct?: boolean) {
// 激活组件实现...
}
export function deactivateChildComponent (vm: Component, direct?: boolean) {
// 停用组件实现...
}
这两个函数用于处理 keep-alive 组件的激活和停用:
- 递归地处理子组件的激活/停用
- 触发
activated/deactivated钩子 - 管理
_inactive标志状态
生命周期流程图解
Vue 实例生命周期流程可以概括为:
创建实例 → 初始化事件和生命周期 → 初始化数据 → 编译模板 → 挂载DOM → 数据更新 → 销毁实例
对应到代码调用流程:
new Vue() → initLifecycle() → mountComponent() →
beforeCreate → created → beforeMount →
(Watcher创建) → _render() → _update() →
mounted → ... → beforeUpdate → updated → ... →
beforeDestroy → destroyed
重要设计特点
1. 组件实例结构设计
Vue 实例通过层层递进的属性标志清晰地标记组件状态:
_isMounted:是否已挂载_isDestroyed:是否已销毁_isBeingDestroyed:是否正在销毁过程中
这种设计使得生命周期状态判断高效且清晰。
2. 父子组件关系管理
通过 $parent、$children、$root 建立组件树结构,同时处理了抽象组件的特殊情况,保证组件层次的正确性。
3. activeInstance 机制
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance
activeInstance = vm
return () => {
activeInstance = prevActiveInstance
}
}
这种设计创建了一个临时的"当前活动实例"上下文:
- 在组件渲染过程中跟踪当前实例
- 返回一个恢复函数,确保嵌套组件渲染完成后能正确恢复父级上下文
- 实现了类似"作用域"的概念,保证多层组件嵌套时的正确性
4. 渲染 Watcher 机制
渲染 Watcher 是连接响应式系统和视图的桥梁:
- 在数据变化时自动触发组件更新
- 通过
before钩子在更新前触发生命周期事件 - 使用统一的渲染函数,简化内部实现
5. 钩子事件机制
除了直接调用选项中的钩子函数外,Vue 还提供了钩子事件机制:
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
这使得父组件可以监听子组件的生命周期事件:
<child-component @hook:mounted="onChildMounted" />
增强了组件间的联动能力。
总结
lifecycle.js 文件是 Vue 生命周期机制的核心实现,它通过精心设计的实例属性和方法,实现了组件从创建、挂载、更新到销毁的完整生命周期管理:
- 组件关系管理:建立清晰的父子组件树结构
- 状态标记系统:使用一系列标志位精确跟踪组件状态
- 渲染机制:通过 Watcher 连接数据变化与视图更新
- 钩子调用系统:确保生命周期钩子在正确的时机被调用
- 资源管理:完善的销毁机制防止内存泄漏
Vue 的生命周期实现体现了框架的设计哲学:简洁的 API 背后是精心设计的内部机制,使开发者能够轻松掌控组件的整个生命周期。