1. vmCount 的核心作用
vmCount
是专门用于 标记当前响应式对象被作为组件根数据($data)引用的次数。它本质上是一个引用计数器,主要解决以下三个关键问题:
- 防止运行时动态添加/删除根数据的响应式属性(set/del 方法中的警告)
- 跟踪共享数据的组件引用情况(observe 中的计数)
- 组件销毁时正确释放引用($destroy 中的递减)
2. 完整生命周期流程
(1) 初始化阶段 - Observer 构造函数
ini
var Observer = function Observer(value) {
this.vmCount = 0; // 初始化为0
}
每个被观察对象创建时,vmCount
初始为0,表示尚未被任何组件作为根数据使用。
(2) 组件挂载阶段 - observe()
scss
function observe(value, asRootData) {
// ...
if (asRootData && ob) {
ob.vmCount++; // 关键递增操作
}
}
当数据作为组件根数据(data
选项)被观察时:
asRootData
参数为 true- 对应的
__ob__.vmCount
会 +1 - 典型场景 :
new Vue({ data: {...} })
初始化时
(3) 组件销毁阶段 - $destroy()
javascript
Vue.prototype.$destroy = function() {
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--; // 关键递减操作
}
}
组件销毁时:
- 将关联数据的
vmCount
减1 - 内存管理:防止已销毁组件仍被计数影响判断
(4) 运行时检测 - set()/del()
scss
// set 方法
if (target._isVue || (ob && ob.vmCount)) {
warn('Avoid adding reactive properties...');
}
// del 方法
if (target._isVue || (ob && ob.vmCount)) {
warn('Avoid deleting properties...');
}
当检测到 ob.vmCount > 0
时:
- 阻止对根数据动态添加/删除属性
- 设计原因:根数据应在 data 选项中预先声明,确保响应式系统完整性
3. 设计原理图解
scss
组件实例1 ($data) ──────┐
├─→ 共享数据 { a:1 } (vmCount=2)
组件实例2 ($data) ──────┘
│
普通对象引用 ────────────→ 不增加 vmCount
4. 实际应用示例
场景1:组件共享数据
javascript
const sharedData = { foo: 1 }
new Vue({ data: () => sharedData }) // sharedData.__ob__.vmCount → 1
new Vue({ data: () => sharedData }) // sharedData.__ob__.vmCount → 2
场景2:动态属性警告
ini
const vm = new Vue({ data: { msg: 'hello' } })
vm.$data.newProp = 'value' // 触发警告:Avoid adding reactive properties...
// 因为 vm.$data.__ob__.vmCount === 1
5. 总结设计意图
- 声明式约束:强制在 data 选项中预先声明所有响应式属性,避免运行时意外行为
- 引用追踪:精确管理共享数据的组件引用关系
- 性能优化:快速判断对象是否作为根数据使用,优化响应式处理逻辑
- 内存安全:组件销毁时正确释放引用计数,避免内存泄漏
这种设计体现了 Vue 响应式系统的严谨性,通过 vmCount
这个简单的计数器,优雅地解决了根数据管理的复杂问题。