Vue.js响应式核心是数据“可追踪”和“可通知”:Vue2用Object.defineProperty递归劫持对象属性并重写数组变异方法,Vue3则用Proxy全面接管,提升性能并解决限制。

Vue.js 的响应式实现,核心在于让数据“可追踪”和“可通知”。对象与数组的深度劫持不是简单地加个监听器,而是通过一套有层次、有取舍的机制,确保变化能被准确捕获并触发视图更新。
对象的深度劫持:递归 + defineProperty
Vue2 使用 Object.defineProperty 对对象每个属性做单层劫持。但普通对象可能嵌套多层,比如 user.profile.address.city。为保证深层属性变化也能响应,Vue 在初始化时递归遍历:
- 遇到普通对象,就调用
observe创建Observer实例,并用walk遍历其所有自有属性 - 对每个属性执行
defineReactive,设置 getter(收集依赖)和 setter(派发更新) - 若属性值仍是对象或数组,就再次进入
observe,形成递归观测链
这种设计保障了任意层级的数据修改都能触发更新,但代价是初始化时需遍历全部属性——对象越深、属性越多,首次渲染越慢。
数组的特殊处理:重写变异方法
数组不能靠单纯劫持索引(如 arr[0])来实现响应式,因为索引是数字键,且大量索引逐个 define 会带来巨大性能开销(比如 new Array(10000))。Vue2 的解法是:
立即学习“前端免费学习笔记(深入)”;
- 不劫持数组索引本身,而是拦截 7 个会改变原数组的方法:
push、pop、shift、unshift、splice、sort、reverse - 在原型上重写这些方法,内部先调用原生逻辑,再手动触发依赖更新
- 对通过
arr[i] = xxx或arr.length = xxx的修改,无法自动检测,必须用Vue.set或vm.$set
这种方式兼顾了可用性与性能,避免了为每个数组项添加 getter/setter 的开销。
Vue3 的升级:Proxy 全面接管
Vue3 放弃 Object.defineProperty,改用 Proxy 实现响应式:
- Proxy 可直接代理整个对象或数组,无需递归定义每个属性;新增/删除属性天然可追踪
- 数组索引赋值(
arr[0] = 1)、长度修改(arr.length = 0)均可被拦截,不再需要Vue.set -
reactive()可安全包裹对象和数组,ref()则用于基础类型,两者内部都基于 Proxy 构建
这不仅解决了 Vue2 的诸多限制,还提升了约 40% 的响应式性能(官方基准测试结果)。
实际开发中的注意点
即便理解了原理,日常编码仍需避开常见陷阱:
- Vue2 中向响应式对象添加新属性,必须用
this.$set(obj, key, val),否则无响应 - 直接通过索引设置数组项(
arr[1] = 'x')或修改 length,在 Vue2 中无效 - 使用
Object.freeze()后的对象不会被劫持,因为它禁止属性变更,defineProperty 会失败 - Proxy 不兼容 IE,这是 Vue3 放弃 IE 支持的技术原因之一


















