深入理解 Proxy 与 Reflect
13个拦截器完整用法、Vue3响应式原理、可撤销代理与不变量约束
什么是 Proxy 与 Reflect?
定义:Proxy 是 ES6 引入的元编程机制,允许你创建一个对象的"代理",通过 13 种拦截器(trap)自定义该对象的基本操作(读取、赋值、删除、枚举等)。Reflect 是与 Proxy 配套的内置对象,提供了与 13 种拦截器一一对应的静态方法,用于执行对象的默认行为。
涉及场景:
- Vue3 响应式系统:
reactive()底层使用Proxy拦截 get/set 实现依赖收集和派发更新 - 数据验证:拦截
set操作,在赋值前校验数据类型和范围 - 访问控制:拦截
get操作,实现属性的只读、权限控制 - 日志追踪:自动记录对象的读写操作,用于调试和审计
- 默认值处理:访问不存在的属性时返回默认值而非
undefined - 不可变数据:创建深度冻结的对象,任何修改都抛出错误
作用:
- 替代
Object.defineProperty:无需预先知道属性名,可拦截新增属性、数组操作等 - 元编程能力:从语言层面自定义对象的底层行为
- AOP 编程:在不修改原对象的前提下,添加横切关注点(日志、缓存、验证)
- 框架核心:现代前端框架响应式系统的基石
javascript
const target = { name: '张三', age: 25 };
const proxy = new Proxy(target, {
get(target, prop, receiver) {
console.log(`读取了 ${String(prop)}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置 ${String(prop)} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});
proxy.name; // 读取了 name → '张三'
proxy.age = 26; // 设置 age = 26什么是 Reflect?
Reflect 是一个内置对象,提供了与 Proxy handler 一一对应的静态方法。它的设计目的:
- 将 Object 的命令式操作变为函数式 —
Reflect.has(obj, key)替代key in obj - 提供默认行为 — 在 Proxy handler 中调用
Reflect执行默认操作 - 返回布尔值替代抛异常 —
Reflect.defineProperty()返回true/false,而不是抛错
javascript
// Object 操作 → Reflect 等价
'name' in obj → Reflect.has(obj, 'name')
delete obj.name → Reflect.deleteProperty(obj, 'name')
Object.keys(obj) → Reflect.ownKeys(obj) // 注意:包含Symbol
obj.name → Reflect.get(obj, 'name')
obj.name = 'value' → Reflect.set(obj, 'name', 'value')
new Constructor(...args) → Reflect.construct(Constructor, args)
fn.apply(ctx, args) → Reflect.apply(fn, ctx, args)13个拦截器(Trap)完整列表
1. get — 读取属性
javascript
const handler = {
// target: 目标对象
// prop: 属性名(string 或 Symbol)
// receiver: proxy 本身或继承 proxy 的对象
get(target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
throw new ReferenceError(`属性 "${String(prop)}" 不存在`);
}
};
const obj = new Proxy({ name: '张三' }, handler);
obj.name; // '张三'
// obj.xxx; // ❌ ReferenceError: 属性 "xxx" 不存在2. set — 设置属性
javascript
const handler = {
// value: 设置的值
// 返回 true 表示成功,false 会在严格模式下抛 TypeError
set(target, prop, value, receiver) {
if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
throw new TypeError('age 必须是非负数');
}
return Reflect.set(target, prop, value, receiver);
}
};
const user = new Proxy({}, handler);
user.name = '张三'; // ✅
user.age = 25; // ✅
// user.age = -1; // ❌ TypeError: age 必须是非负数
// user.age = 'old'; // ❌ TypeError3. has — in 操作符
javascript
const handler = {
has(target, prop) {
// 隐藏以 _ 开头的私有属性
if (typeof prop === 'string' && prop.startsWith('_')) {
return false;
}
return Reflect.has(target, prop);
}
};
const obj = new Proxy({ _secret: 42, name: '张三' }, handler);
console.log('name' in obj); // true
console.log('_secret' in obj); // false(被隐藏了)4. deleteProperty — delete 操作符
javascript
const handler = {
deleteProperty(target, prop) {
if (typeof prop === 'string' && prop.startsWith('_')) {
throw new Error(`不能删除私有属性 ${prop}`);
}
return Reflect.deleteProperty(target, prop);
}
};
const obj = new Proxy({ _id: 1, name: '张三' }, handler);
delete obj.name; // ✅
// delete obj._id; // ❌ Error: 不能删除私有属性 _id5. ownKeys — 枚举属性
javascript
const handler = {
// 拦截: Object.keys(), Object.getOwnPropertyNames(),
// Object.getOwnPropertySymbols(), Reflect.ownKeys()
ownKeys(target) {
// 过滤掉 _ 开头的属性
return Reflect.ownKeys(target).filter(
key => typeof key !== 'string' || !key.startsWith('_')
);
}
};
const obj = new Proxy({ _id: 1, name: '张三', age: 25 }, handler);
console.log(Object.keys(obj)); // ['name', 'age'](_id 被隐藏)6. getOwnPropertyDescriptor — 属性描述符
javascript
const handler = {
getOwnPropertyDescriptor(target, prop) {
// 隐藏私有属性的描述符
if (typeof prop === 'string' && prop.startsWith('_')) {
return undefined;
}
return Reflect.getOwnPropertyDescriptor(target, prop);
}
};7. defineProperty — 定义属性
javascript
const handler = {
defineProperty(target, prop, descriptor) {
// 阻止定义不可枚举的属性
if (descriptor.enumerable === false) {
throw new Error('所有属性必须是可枚举的');
}
return Reflect.defineProperty(target, prop, descriptor);
}
};8. getPrototypeOf — 获取原型
javascript
const handler = {
getPrototypeOf(target) {
// 拦截: Object.getPrototypeOf(), __proto__, instanceof
return Reflect.getPrototypeOf(target);
}
};9. setPrototypeOf — 设置原型
javascript
const handler = {
setPrototypeOf(target, proto) {
// 禁止修改原型
throw new Error('不允许修改原型');
}
};
const obj = new Proxy({}, handler);
// Object.setPrototypeOf(obj, {}); // ❌ Error10. isExtensible — 是否可扩展
javascript
const handler = {
isExtensible(target) {
return Reflect.isExtensible(target);
}
};11. preventExtensions — 阻止扩展
javascript
const handler = {
preventExtensions(target) {
return Reflect.preventExtensions(target);
}
};12. apply — 函数调用(仅函数代理)
javascript
const handler = {
// target: 目标函数
// thisArg: this
// args: 参数列表
apply(target, thisArg, args) {
console.log(`调用 ${target.name}(${args.join(', ')})`);
const start = performance.now();
const result = Reflect.apply(target, thisArg, args);
console.log(`耗时: ${(performance.now() - start).toFixed(2)}ms`);
return result;
}
};
function sum(a, b) { return a + b; }
const proxySum = new Proxy(sum, handler);
proxySum(1, 2);
// 调用 sum(1, 2)
// 耗时: 0.01ms
// → 313. construct — new 操作(仅函数代理)
javascript
const handler = {
// target: 目标构造函数
// args: 参数列表
// newTarget: 被调用的构造函数(通常等于 proxy)
construct(target, args, newTarget) {
console.log(`new ${target.name}(${args.join(', ')})`);
return Reflect.construct(target, args, newTarget);
}
};
function Person(name) { this.name = name; }
const ProxyPerson = new Proxy(Person, handler);
const p = new ProxyPerson('张三');
// new Person(张三)实战应用
1. Vue3 响应式原理(简化版)
javascript
// Vue3 的 reactive() 核心实现
const targetMap = new WeakMap();
let activeEffect = null;
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const result = Reflect.get(target, key, receiver);
// 深层代理
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 = key in target;
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
}
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 使用
const state = reactive({ count: 0, name: '张三' });
effect(() => {
console.log(`count = ${state.count}`);
});
// 立即输出: count = 0
state.count++; // 自动输出: count = 1
state.count++; // 自动输出: count = 22. 数据验证器
javascript
function createValidator(schema) {
return new Proxy({}, {
set(target, prop, value) {
const rule = schema[prop];
if (!rule) {
throw new Error(`未知属性: ${prop}`);
}
// 类型检查
if (rule.type && typeof value !== rule.type) {
throw new TypeError(`${prop} 必须是 ${rule.type},收到 ${typeof value}`);
}
// 范围检查
if (rule.min !== undefined && value < rule.min) {
throw new RangeError(`${prop} 最小值为 ${rule.min}`);
}
if (rule.max !== undefined && value > rule.max) {
throw new RangeError(`${prop} 最大值为 ${rule.max}`);
}
// 正则检查
if (rule.pattern && !rule.pattern.test(value)) {
throw new Error(`${prop} 格式不正确`);
}
// 自定义验证
if (rule.validate && !rule.validate(value)) {
throw new Error(`${prop} 验证失败: ${rule.message || '无效值'}`);
}
return Reflect.set(target, prop, value);
}
});
}
const user = createValidator({
name: { type: 'string', pattern: /^.{2,20}$/ },
age: { type: 'number', min: 0, max: 150 },
email: { type: 'string', pattern: /^\S+@\S+\.\S+$/ }
});
user.name = '张三'; // ✅
user.age = 25; // ✅
user.email = 'test@example.com'; // ✅
// user.age = -1; // ❌ RangeError
// user.email = 'invalid'; // ❌ Error: email 格式不正确3. 负索引数组
javascript
function createNegativeArray(arr) {
return new Proxy(arr, {
get(target, prop, receiver) {
const index = Number(prop);
if (Number.isInteger(index) && index < 0) {
// 负索引:-1 表示最后一个元素
return Reflect.get(target, target.length + index, receiver);
}
return Reflect.get(target, prop, receiver);
}
});
}
const arr = createNegativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
console.log(arr[0]); // 14. 属性访问链安全(可选链替代方案)
javascript
function safe(obj) {
return new Proxy(obj, {
get(target, prop) {
const value = Reflect.get(target, prop);
if (value === undefined || value === null) {
return safe({}); // 返回空代理,避免 TypeError
}
if (typeof value === 'object') {
return safe(value);
}
return value;
}
});
}
const data = { a: { b: { c: 42 } } };
console.log(safe(data).a.b.c); // 42
console.log(safe(data).x.y.z); // Proxy{}(不会报错)
// 现在推荐用可选链 data?.a?.b?.c,但这展示了 Proxy 的能力5. 不可变对象(深冻结)
javascript
function deepFreeze(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'object' && value !== null) {
return deepFreeze(value); // 递归代理
}
return value;
},
set() {
throw new Error('对象不可修改');
},
deleteProperty() {
throw new Error('对象不可删除属性');
},
defineProperty() {
throw new Error('对象不可定义新属性');
}
});
}
const frozen = deepFreeze({ a: { b: { c: 1 } } });
console.log(frozen.a.b.c); // 1
// frozen.a.b.c = 2; // ❌ Error: 对象不可修改可撤销代理(Proxy.revocable)
javascript
const { proxy, revoke } = Proxy.revocable({ name: '张三' }, {
get(target, prop) {
return Reflect.get(target, prop);
}
});
console.log(proxy.name); // '张三'
revoke(); // 撤销代理
// proxy.name; // ❌ TypeError: Cannot perform 'get' on a proxy that has been revoked
// proxy.name = '李四'; // ❌ TypeError
// 适用场景:
// 1. 临时授权:给第三方库临时访问权限,用完后撤销
// 2. 资源管理:确保代理不会在不需要时被继续使用
// 3. 安全隔离:API token 过期后撤销代理Proxy 不变量(Invariant)
Proxy 的 handler 不能违反某些规则,否则会抛出 TypeError:
javascript
// 1. get:不可配置且不可写的属性,代理必须返回原始值
const obj = {};
Object.defineProperty(obj, 'x', {
value: 42,
writable: false,
configurable: false
});
const proxy = new Proxy(obj, {
get() { return 100; } // ❌ 试图返回不同值
});
// proxy.x; // TypeError: 'get' on proxy: property 'x' is a read-only
// // and non-configurable data property on the proxy target
// // but the proxy did not return its actual value
// 2. set:不可配置且不可写的属性不能被修改
// 3. has:不可配置的属性不能被隐藏
// 4. deleteProperty:不可配置的属性不能被删除
// 5. ownKeys:必须包含目标对象所有不可配置的自有属性
// 6. getPrototypeOf:如果目标不可扩展,必须返回实际原型为什么用 Reflect 而不直接操作 target?
javascript
// ❌ 直接操作可能出问题
const proxy = new Proxy(obj, {
get(target, prop) {
return target[prop]; // 可能绕过其他代理层
},
set(target, prop, value) {
target[prop] = value; // 不返回布尔值
return true;
}
});
// ✅ 使用 Reflect
const proxy2 = new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
// 1. 正确传递 receiver(处理 getter 中的 this)
// 2. 返回值语义正确
},
set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
// 1. 正确传递 receiver(处理 setter 中的 this)
// 2. 自动返回布尔值
}
});
// receiver 的重要性
const parent = {
get value() {
return this._value; // this 应该指向谁?
}
};
const child = Object.create(
new Proxy(parent, {
get(target, prop, receiver) {
console.log('receiver is child?', receiver === child);
// 使用 Reflect.get 时 receiver 正确传递
return Reflect.get(target, prop, receiver);
}
})
);
child._value = 42;
child.value; // receiver is child? true → 42
// 如果用 target[prop],this 会指向 parent,而不是 childProxy vs Object.defineProperty
javascript
// Vue2 用 Object.defineProperty,Vue3 用 Proxy
// Object.defineProperty 的局限:
const obj = { a: 1 };
// 1. 必须遍历每个属性
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get() { return value; },
set(newVal) { value = newVal; }
});
});
// 2. ❌ 无法监听新增属性
obj.b = 2; // 不会触发 setter(Vue2 的 $set 就是为了解决这个问题)
// 3. ❌ 无法监听数组索引变化
const arr = [1, 2, 3];
arr[0] = 10; // 不会触发(Vue2 重写了数组方法)
// 4. ❌ 无法监听 delete
delete obj.a; // 不会触发
// Proxy 全部支持:
const proxyObj = new Proxy({}, {
get(t, p, r) { return Reflect.get(t, p, r); },
set(t, p, v, r) {
console.log('set', p, v); // ✅ 新增属性也能监听
return Reflect.set(t, p, v, r);
},
deleteProperty(t, p) {
console.log('delete', p); // ✅ 删除也能监听
return Reflect.deleteProperty(t, p);
}
});
proxyObj.newProp = 'hello'; // ✅ set newProp hello
delete proxyObj.newProp; // ✅ delete newProp| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 监听新增属性 | ❌ | ✅ |
| 监听删除属性 | ❌ | ✅ |
| 监听数组索引 | ❌ | ✅ |
| 深层监听 | 需递归 | 惰性递归(性能更好) |
| 兼容性 | IE9+ | IE 完全不支持 |
| 性能 | 初始化开销大 | 访问时拦截 |
Reflect 全部 API
javascript
Reflect.get(target, prop, receiver) // 读取属性
Reflect.set(target, prop, value, receiver) // 设置属性
Reflect.has(target, prop) // in 操作符
Reflect.deleteProperty(target, prop) // delete 操作符
Reflect.ownKeys(target) // 所有自有属性键
Reflect.getOwnPropertyDescriptor(target, prop) // 属性描述符
Reflect.defineProperty(target, prop, desc) // 定义属性
Reflect.getPrototypeOf(target) // 获取原型
Reflect.setPrototypeOf(target, proto) // 设置原型
Reflect.isExtensible(target) // 是否可扩展
Reflect.preventExtensions(target) // 阻止扩展
Reflect.apply(fn, thisArg, args) // 调用函数
Reflect.construct(Target, args, newTarget) // new 操作总结
Proxy & Reflect 核心要点:
┌──────────────────────────────────────────────────────────┐
│ Proxy │
│ • 13个 trap 拦截对象的所有基本操作 │
│ • 不修改原对象,创建代理层 │
│ • 支持数组、函数、Map/Set 等所有对象类型 │
│ • Proxy.revocable() 创建可撤销代理 │
│ • 有不变量约束,不能违反 JS 引擎的规则 │
├──────────────────────────────────────────────────────────┤
│ Reflect │
│ • 与 Proxy trap 一一对应的静态方法 │
│ • 提供默认行为,在 handler 中调用 │
│ • 正确传递 receiver(处理 getter/setter 中的 this) │
│ • 返回布尔值而非抛异常(比 Object 方法更安全) │
├──────────────────────────────────────────────────────────┤
│ 实战应用 │
│ • Vue3 响应式系统(reactive/ref/computed) │
│ • 数据验证 / 类型检查 │
│ • 访问控制 / 权限管理 │
│ • 日志记录 / 性能监控 │
│ • 不可变数据结构 │
│ • 负索引数组 / 默认值 / 缓存 │
└──────────────────────────────────────────────────────────┘