前言
Proxy 是 ES6 引入的元编程利器,它能拦截并自定义对象的基本操作,从属性读写到函数调用再到 in 运算符,几乎无所不能。Vue3 的响应式系统正是基于 Proxy 实现的。本文将全面拆解 Proxy 的原理和实际应用。
Proxy 基础
什么是 Proxy
Proxy 在目标对象之前架设一层”拦截”,外界对该对象的访问都必须先通过这层拦截:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const target = { name: 'hexo' } const handler = { get(target, key, receiver) { console.log(`读取属性: ${String(key)}`) return target[key] }, set(target, key, value, receiver) { console.log(`设置属性: ${String(key)} = ${value}`) target[key] = value return true } } const proxy = new Proxy(target, handler)
proxy.name proxy.age = 25
|
关键点:对 proxy 进行操作才会触发拦截,直接操作原始对象 target 不会。
Reflect 的重要性
Reflect 是搭配 Proxy 的最佳拍档,它提供了一套与 Proxy 捕获器一一对应的静态方法:
1 2 3 4 5 6 7 8
| const proxy = new Proxy(target, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { return Reflect.set(target, key, value, receiver) } })
|
使用 Reflect 的三个理由:
- 保持 this 绑定正确 —
receiver 参数确保 getter 中的 this 指向 proxy
- 返回值合理 —
Reflect.set 返回 true/false,而直接赋值可能静默失败
- 代码语义统一 — 不再混用
delete obj[key]、key in obj 等操作符语法
13 种捕获器详解
Proxy 共支持 13 种捕获器,覆盖了对对象的所有操作。
属性相关
| 捕获器 |
拦截操作 |
触发场景 |
get(target, key, receiver) |
读取属性 |
proxy.key、proxy[key] |
set(target, key, value, receiver) |
设置属性 |
proxy.key = val |
has(target, key) |
in 运算符 |
'key' in proxy |
deleteProperty(target, key) |
delete |
delete proxy.key |
getOwnPropertyDescriptor(target, key) |
属性描述符 |
Object.getOwnPropertyDescriptor() |
对象结构相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const proxy = new Proxy(target, { ownKeys(target) { return Reflect.ownKeys(target).filter(k => k !== 'password') }, defineProperty(target, key, descriptor) { return Reflect.defineProperty(target, key, descriptor) }, isExtensible(target) { return Reflect.isExtensible(target) }, preventExtensions(target) { return Reflect.preventExtensions(target) } })
|
原型与函数相关
| 捕获器 |
拦截操作 |
getPrototypeOf(target) |
Object.getPrototypeOf()、__proto__ |
setPrototypeOf(target, proto) |
Object.setPrototypeOf() |
apply(target, thisArg, args) |
proxy(...args)、proxy.call()、proxy.apply() |
construct(target, args, newTarget) |
new proxy(...args) |
apply 和 construct 只在 Proxy 代理的是函数时生效。
实战场景
场景一:数据验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function createValidator(schema) { return new Proxy({}, { set(target, key, value) { const rule = schema[key] if (rule && !rule.test(value)) { throw new Error(`${String(key)}: ${rule.message}`) } return Reflect.set(target, key, value) } }) }
const user = createValidator({ age: { test: v => v >= 0 && v <= 120, message: '年龄需在 0-120 之间' }, name: { test: v => v.length >= 2, message: '姓名至少 2 个字符' } })
user.name = '张三' user.age = 150
|
场景二:负索引数组
1 2 3 4 5 6 7 8 9 10 11 12 13
| function createSmartArray(arr) { return new Proxy(arr, { get(target, key, receiver) { let index = Number(key) if (index < 0) index = target.length + index return Reflect.get(target, String(index), receiver) } }) }
const arr = createSmartArray(['a', 'b', 'c', 'd']) console.log(arr[-1]) console.log(arr[-2])
|
场景三:观察者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function observe(target, onChange) { return new Proxy(target, { set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (oldValue !== value) { onChange({ key, oldValue, newValue: value }) } return result }, deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key) if (hadKey) { onChange({ key, type: 'delete' }) } return result } }) }
const state = observe({ count: 0 }, (change) => { console.log('数据变更:', change) })
state.count++
|
场景四:API 缓存代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function memoize(fn, ttl = 5000) { const cache = new Map() return new Proxy(fn, { apply(target, thisArg, args) { const key = JSON.stringify(args) const cached = cache.get(key) if (cached && Date.now() - cached.time < ttl) { console.log('命中缓存') return cached.value } const result = Reflect.apply(target, thisArg, args) cache.set(key, { value: result, time: Date.now() }) return result } }) }
|
Vue3 中 Proxy 的应用
Vue3 的响应式系统完全基于 Proxy 构建,这里拆解关键实现。
1. reactive 核心实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| function reactive(target) { if (typeof target !== 'object' || target === null) return target
return new Proxy(target, { get(target, key, receiver) { const result = Reflect.get(target, key, receiver) track(target, key) if (typeof result === 'object' && result !== null) { return reactive(result) } return result },
set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (oldValue !== value) { trigger(target, key) } return result },
deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key) if (hadKey) { trigger(target, key) } return result },
has(target, key) { track(target, key) return Reflect.has(target, key) },
ownKeys(target) { track(target, Symbol('iterate')) return Reflect.ownKeys(target) } }) }
|
2. Vue3 响应式数据流
1 2 3 4 5 6 7 8 9 10 11 12 13
| const state = reactive({ count: 0, info: { name: 'Vue3' } })
effect(() => { console.log(state.count) })
state.count++
state.info state.info.name
|
三级依赖存储结构:
| 层级 |
结构 |
键 |
值 |
targetMap |
WeakMap |
target 对象 |
depsMap |
depsMap |
Map |
属性名 key |
dep (Set) |
dep |
Set |
— |
effect 函数 |
使用 WeakMap 存储 target → deps 映射,当原对象被销毁时依赖自动 GC,避免内存泄漏。
3. ref 也依赖 Proxy(对象类型时)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function ref(value) { if (typeof value === 'object') { return reactive({ value }) } return { get value() { track(this, 'value'); return value }, set value(v) { value = v; trigger(this, 'value') } } }
const obj = ref({ name: 'hello' }) obj.value.name = 'world'
|
4. shallowReactive 和 readonly 的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function shallowReactive(target) { return new Proxy(target, { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) } }) }
function readonly(target) { return new Proxy(target, { set() { return true }, deleteProperty() { return true } }) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> import { reactive, shallowReactive, readonly } from 'vue'
// 深层响应,适合复杂表单 const form = reactive({ name: '', addr: { city: '' } })
// 浅层响应,适合大量数据只改引用 const list = shallowReactive([...bigData])
// props 本质是 readonly 的 reactive const props = defineProps({ title: String }) // props.title = 'new' → 静默失败,不会触发更新 </script>
|
5. toRaw / markRaw 绕过 Proxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { reactive, toRaw, markRaw } from 'vue'
const obj = reactive({ id: 1, name: 'test' })
// toRaw 拿到 Proxy 背后的原始对象 const raw = toRaw(obj) raw.name = 'changed' // 不会触发更新
// markRaw 标记对象永不代理(如第三方实例、大体积数据) const staticData = markRaw({ huge: '...' }) const state = reactive({ data: staticData }) // state.data 不会被代理,减少性能开销 </script>
|
注意事项
Proxy 无法代理的情况
- 原始值不能直接代理 — 必须包装为对象(这也是 Vue3 中
ref 存在的意义之一)
- Map、Set 等内置对象 — 代理时需要特殊处理,否则内部
this 会指错
- 私有属性
# — Proxy 无法拦截私有字段的访问
性能考量
| 操作 |
无代理 |
有代理 |
开销 |
| 读属性 |
~0.2ns |
~5ns |
约 25x |
| 写属性 |
~0.3ns |
~8ns |
约 27x |
代价虽比直接访问高,但绝对速度仍然极快,正常业务场景无需担心。避免在高频循环中对同一个对象反复创建 Proxy 即可。
总结
- Proxy 提供了 13 种捕获器,可以拦截几乎所有的对象底层操作
Reflect 是 Proxy 的最佳搭档,确保行为正确和代码一致
- 实际应用场景包括:数据验证、响应式系统、日志埋点、缓存代理等
- Vue3 响应式正是利用 Proxy 全面代替了
Object.defineProperty
- 注意内置对象代理的兼容性问题,避免在高频场景反复创建代理
掌握 Proxy 就掌握了现代前端框架响应式系统的底层密码。
内容由 AI 生成,仅供参考。本文发布于 码上学习,转载请注明出处。