vue 2 的响应式核心是 object.defineproperty,通过 get/set 拦截属性读写实现依赖收集与更新通知;需递归处理嵌套对象,用 dep 管理 watcher;新增/删除属性及数组索引赋值无法监听,需 vue.set/vue.delete 或重写数组方法;proxy 在 vue 3 中解决了这些限制。

Vue 2 的响应式能力,核心就落在 Object.defineProperty 对属性描述符的精准控制上。它不靠轮询或脏检查,而是通过劫持数据的读写行为,在关键节点插入依赖收集和更新通知逻辑。
用 get/set 描述符拦截访问与赋值
Vue 在初始化 data 时,对每个属性调用 Object.defineProperty,传入存取描述符(accessor descriptor),即 get 和 set 函数:
-
get:当模板中读取
{{ msg }}或计算属性访问data.msg时触发,此时会把当前正在求值的 Watcher 记录为该属性的依赖; -
set:当执行
this.msg = 'new'时触发,内部比对新旧值,若变化则通知所有已收集的 Watcher 执行更新; - 注意:
value和writable这类数据描述符不能与get/set混用,否则会报错。
递归遍历 + 依赖收集机制
单纯定义单层属性不够,Vue 需要处理嵌套对象和数组:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
- 对对象的每个自有属性(
hasOwnProperty过滤)递归调用defineReactive,确保深层属性也被劫持; - 每个响应式属性都关联一个
Dep实例,用于管理依赖它的 Watcher 列表(subs); - 在 getter 中调用
dep.depend(),让当前活跃的 Watcher 将自己加入该属性的依赖池; - 在 setter 中调用
dep.notify(),遍历所有 Watcher 并触发其update方法。
无法监听的边界情况及应对方式
Object.defineProperty 存在天然限制,Vue 2 必须绕过或补充:
- 新增属性(如
vm.obj.newKey = val)不会触发响应——需用Vue.set(obj, key, val); - 删除属性(
delete vm.obj.key)同样失效——需用Vue.delete(obj, key); - 数组索引直接赋值(
arr[0] = newVal)或修改length不被监听——Vue 重写了push、pop等 7 个变更方法,在内部手动触发更新; - 原型链属性、不可枚举属性、Symbol 键均不会被自动处理。
与 Vue 3 Proxy 的关键差异
这些限制正是 Vue 3 改用 Proxy 的主要原因:
-
Proxy可以代理整个对象,无需逐个定义属性,天然支持动态增删; - 能拦截数组索引、
length变更、in操作符、for...in遍历等更多操作; - 但
Object.defineProperty在 Vue 2 中仍是稳定、可预测的选择,尤其适合 IE9+ 环境。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










