proxy拦截器最核心、必写的两个操作是get和set:get用于读取时依赖收集,set用于写入时触发更新,二者共同构成响应式数据劫持的基础闭环。

Proxy 拦截器核心要拦截哪些操作?
Vue3 响应式底层用 Proxy 代替了 Vue2 的 Object.defineProperty,关键在于精准拦截对象的读取(get)和写入(set)行为,同时还要处理删除(deleteProperty)、in 检查、遍历(ownKeys)、数组索引/长度变更等。但最核心、必写的两个是:get 和 set。
手写简版 reactive:基础 Proxy + 依赖收集
响应式本质 = 数据劫持 + 依赖追踪 + 触发更新。我们聚焦 Proxy 部分,先忽略 effect、track、trigger 的完整调度逻辑,写出可运行的最小闭环:
✅ 关键点:
- get 拦截器:读取属性时,自动将当前副作用函数(如 render)与该属性建立联系(依赖收集)
- set 拦截器:修改属性时,通知所有依赖该属性的副作用函数重新执行
- 递归代理:对嵌套对象/数组也返回 reactive,保证深层响应式
- 避免重复代理:已代理的对象直接返回,防止无限递归
代码示例(精简可运行版):
const toProxy = new WeakMap() // 缓存已代理对象,避免重复
const toRaw = new WeakMap() // 反向映射:代理 → 原对象
function reactive(target) {
if (!isObject(target)) return target
if (toProxy.has(target)) return toProxy.get(target)
if (toRaw.has(target)) return target
const handler = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// ✅ 依赖收集:这里假设有个 activeEffect 存储当前正在执行的副作用
if (activeEffect && key !== '__v_isReactive') {
track(target, key) // 把 activeEffect 记录到 target.key 的依赖列表中
}
// ✅ 深度代理:对对象/数组属性自动 reactive
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const oldVal = target[key]
const res = Reflect.set(target, key, value, receiver)
// ✅ 触发更新:只要值变了(排除 NaN === NaN 场景),就通知依赖
if (value !== oldVal && (oldVal === oldVal || value === value)) {
trigger(target, key) // 通知 target.key 的所有依赖执行
}
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
if (res) trigger(target, key)
return res
},
ownKeys(target) {
track(target, 'length' in target ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
}
const observed = new Proxy(target, handler)
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
几个容易踩坑的细节
Proxy 不是万能的,写拦截器时要注意边界情况:
-
数组索引赋值(
arr[0] = x)和length修改都会触发set,但push/pop/shift/unshift等方法内部会改 length 和索引,需额外在set中判断是否为数组并处理 length 变更 -
for...in / Object.keys() 会触发
ownKeys,必须在这里调用track,否则新增属性后遍历不更新 -
in 操作符 触发
has拦截器,也要 track,否则key in obj响应不及时 -
原始值无法代理:Proxy 只能代理对象,所以
reactive(123)或reactive(null)应直接返回原值 -
避免死循环:在
set中调用trigger后,若副作用里又修改同一属性,需用调度队列或标志位防无限递归
补充:ref 的 Proxy 实现思路
ref(x) 本质是一个带 .value 的响应式包装器。它内部其实也是 Proxy —— 对一个只有 value 属性的普通对象做代理:
function ref(value) {
const refObject = { value }
return new Proxy(refObject, {
get(target, key) {
if (key === 'value') track(refObject, key)
return target[key]
},
set(target, key, newVal) {
if (key === 'value') {
target[key] = newVal
trigger(refObject, key)
return true
}
return false
}
})
}
这样就能让 ref.value 支持响应式读写,且保持和 reactive 统一的依赖系统。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










