Vue2响应式系统深度解析:从Object.defineProperty到依赖收集
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
一、响应式原理概述
Vue2 的响应式系统是基于观察者模式实现的。其核心思想是:当数据发生变化时,自动通知所有依赖这个数据的地方进行更新,从而实现数据驱动视图的自动更新。
核心流程
Loading diagram...
响应式系统解决两个核心问题:
- 依赖收集:谁依赖了这个数据?(getter 阶段)
- 派发更新:数据变化时通知谁?(setter 阶段)
二、Object.defineProperty 基础
API 介绍
Object.defineProperty() 是 Vue2 响应式的基石,它可以精确地添加或修改对象的属性。
语法:
Object.defineProperty(obj, prop, descriptor)参数说明:
obj:要定义属性的对象prop:要定义或修改的属性名称descriptor:属性描述符对象
常用描述符属性:
enumerable:是否可枚举,默认falseconfigurable:是否可配置/删除,默认falsewritable:是否可写,默认falsevalue:属性的值get:getter 函数,访问属性时调用set:setter 函数,设置属性时调用
基础示例
三、Vue2 响应式核心实现
Observer 类:数据劫持
Observer 类负责将对象的所有属性转换为响应式。
为什么 Observer 需要 dep 属性?
defineReactive 中的 dep 是针对单个属性的依赖收集器,而 Observer.dep 是针对整个对象的。它的作用场景包括:
- 数组变异方法:当调用
push、splice等方法时,需要通知所有依赖这个数组的 Watcher Vue.set/$set:动态添加属性时,需要通知依赖这个对象的 Watcher
在 Observer 类中使用 def(value, '__ob__', this) 的目的是:
- 标记对象已被观察:通过
__ob__属性判断对象是否已经被 Observer 处理过,避免重复观察 - 设置为不可枚举:这是关键点!如果
__ob__是可枚举的,那么在walk()方法中遍历对象时:
这会导致无限循环或不必要的处理。通过设置为不可枚举,Object.keys() 不会返回 __ob__,从而避免了这个问题。
示例:
defineReactive:核心劫持函数
observe 函数:统一入口
四、依赖收集系统
Dep 类:依赖管理器
每个响应式属性都有一个对应的 Dep 实例,用于存储所有依赖该属性的 Watcher。
Watcher 类:观察者
Watcher 连接数据和视图,当数据变化时执行回调函数。
依赖收集流程图
Loading diagram...
Vue2 解决方案: 重写了数组的 7 个变异方法(push、pop、shift、unshift、splice、sort、reverse)。
性能问题
- 需要递归遍历整个对象,为每个属性添加 getter/setter
- 嵌套层级深的对象初始化开销大
- Vue3 使用
Proxy解决了这些问题
七、总结
核心要点
- 数据劫持:通过
Object.defineProperty劫持对象属性的 getter/setter - 依赖收集:在 getter 中收集依赖(Dep.depend),记录哪些 Watcher 使用了该数据
- 派发更新:在 setter 中通知依赖(Dep.notify),触发 Watcher 更新
- 观察者模式:Observer、Dep、Watcher 三者协作完成响应式
三大核心类
| 类 | 职责 | 核心方法 |
| Observer | 数据劫持,将对象转为响应式 | walk()、observeArray() |
| Dep | 依赖管理器,存储和通知 Watcher | depend()、notify() |
| Watcher | 观察者,连接数据与视图 | get()、update() |
流程总结
初始化 → Observer 劫持数据 → 渲染触发 getter → Dep 收集依赖 →
数据修改触发 setter → Dep 通知 Watcher → Watcher 执行更新