Vue 中的“数据代理”(Data Proxy)实现与应用详解
在 Vue(尤其是 Vue 2)中,数据代理 和 数据劫持/响应式 是两个密切相关但又不同的概念。很多人把它们混在一起,今天我们彻底拆开来讲清楚。
1. 什么是“数据代理”?(Vue 2 & Vue 3 通用概念)
数据代理:让 vm(Vue 实例)直接代理访问 data 对象里的属性。
写法对比:
// Vue 2 / Vue 3 都支持这种写法
const vm = new Vue({
data: {
msg: "hello",
user: { name: "重阳" }
}
})
// 不用写 vm.$data.msg
console.log(vm.msg) // hello ← 代理后直接访问
vm.msg = "hi" // 也能直接修改
没有数据代理时的写法(原始):
console.log(vm.$data.msg) // 麻烦
vm.$data.msg = "hi"
核心目的:让开发者书写更简洁、自然,像操作普通对象一样操作 Vue 实例。
2. Vue 2 中的数据代理实现(Object.defineProperty + 循环)
Vue 2 在 _initData 阶段做了两件事:
- 数据劫持(响应式):用
Object.defineProperty把data每个属性改造成带 getter/setter 的 - 数据代理:把
data的每个 key 代理到vm实例上
简化核心代码(伪代码风格):
function initData(vm) {
const data = vm.$options.data.call(vm) // 执行 data 函数得到对象
vm._data = data // 内部保存一份
// 第一步:数据代理(让 vm.msg → vm._data.msg)
for (let key in data) {
proxy(vm, '_data', key)
}
// 第二步:响应式劫持(observe)
observe(data)
}
// 代理函数(最核心的三行)
function proxy(target, sourceKey, key) {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return this[sourceKey][key] // vm.msg → vm._data.msg
},
set(newVal) {
this[sourceKey][key] = newVal // vm.msg = xx → vm._data.msg = xx
}
})
}
结论:Vue 2 的数据代理本质是用 Object.defineProperty 在 Vue 实例上为每个 data 属性定义了 getter/setter,只是不做依赖收集,只做转发。
3. Vue 3 中的数据代理(Proxy 时代的变化)
Vue 3 完全抛弃了 Object.defineProperty 的属性级劫持,改用 Proxy 代理整个对象。
但“数据代理”这个概念依然存在,只是实现方式变了。
Vue 3 源码中(简化):
// reactive 函数返回的就是代理对象
export function reactive<T extends object>(target: T): T {
return createReactiveObject(target, ...)
}
const reactiveMap = new WeakMap()
function createReactiveObject(target) {
// ... 省略边界判断
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 1. 依赖收集 track()
track(target, key)
const res = Reflect.get(target, key, receiver)
// 如果是对象,继续代理(深度响应式)
if (isObject(res)) {
return reactive(res) // 惰性代理
}
return res
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
const result = Reflect.set(target, key, value, receiver)
// 2. 触发更新 trigger()
if (oldValue !== value) {
trigger(target, key)
}
return result
},
// deleteProperty、has、ownKeys 等也都有拦截
})
reactiveMap.set(target, proxy)
return proxy
}
Vue 3 里“数据代理”体现在哪里?
在 setup / ref/reactive 返回的对象,最终挂载到组件实例 proxyRefs / setupState 上时,也做了类似代理,让 this.msg 可以直接访问 setup 返回的属性。
但更常见的是:开发者直接使用 reactive 返回的 proxy 对象,不再需要额外的“代理到 vm”这一步。
4. Vue 2 vs Vue 3 数据代理 & 响应式对比(面试必背表)
| 维度 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) | 胜出方 |
|---|---|---|---|
| 劫持粒度 | 属性级(必须先遍历 data) | 对象级(一次 Proxy 搞定整个对象) | Vue 3 |
| 新增属性响应式 | 不支持(需 Vue.set / this.$set) | 天然支持 | Vue 3 |
| 删除属性响应式 | 不支持 | 支持(deleteProperty 拦截) | Vue 3 |
| 数组变化(push 等) | 需要重写 7 个数组方法 | 直接拦截,无需重写 | Vue 3 |
| 嵌套对象响应式 | 递归遍历所有属性(性能开销大) | 惰性递归(访问到才代理) | Vue 3 |
| 性能(大数据对象) | 遍历 + 定义 getter/setter 开销大 | Proxy 创建快,拦截统一 | Vue 3 |
| 数据代理实现 | 显式在 vm 上 defineProperty 转发 | 基本不需要额外代理层(直接用 proxy 对象) | Vue 3 更简洁 |
| 兼容性 | IE9+ | ES6+(现代浏览器) | Vue 2 |
5. 手写极简版 Vue 数据代理(Vue 2 风格 + Vue 3 风格)
Vue 2 风格(数据代理 + 简单响应式):
class MiniVue {
constructor(options) {
this.$data = options.data()
this.initProxy()
// 简单观察者(实际 Vue 复杂得多)
this.observe(this.$data)
}
initProxy() {
Object.keys(this.$data).forEach(key => {
Object.defineProperty(this, key, {
get: () => this.$data[key],
set: val => { this.$data[key] = val; console.log(`${key} 更新了!`) }
})
})
}
observe(obj) {
Object.keys(obj).forEach(key => {
let val = obj[key]
Object.defineProperty(obj, key, {
get: () => val,
set: newVal => { val = newVal; console.log('数据劫持更新') }
})
})
}
}
const app = new MiniVue({
data() { return { count: 0 } }
})
app.count = 100 // 触发代理 set + 数据劫持 set
Vue 3 风格(直接用 Proxy):
function reactive(obj) {
return new Proxy(obj, {
set(target, key, val) {
console.log(`${key} 被修改为 ${val}`)
target[key] = val
// 这里会 trigger 更新视图
return true
}
})
}
const state = reactive({ count: 0, user: { name: '重阳' } })
state.count = 999 // 拦截
state.user.name = '重阳666' // 惰性代理后也能拦截
总结一句话
- Vue 2:数据代理 = 在 vm 实例上用
defineProperty做属性转发 + 响应式劫持 - Vue 3:数据代理的必要性大幅降低,因为 Proxy 本身就是强大的“代理对象”,直接用它就实现了响应式 + 访问便利
重阳,你现在用的是 Vue 2 还是 Vue 3 项目?
想继续深挖:
- 手写更完整的响应式系统(带 dep + watcher)?
- Proxy 的 13 种拦截器在 Vue 里分别怎么用?
- ref vs reactive 的代理区别?
- Vue 3 Proxy 的性能优势具体数据?
随时告诉我,我们继续拆!