Proxy 与 Reflect
问题
Proxy 和 Reflect 是什么?如何使用它们实现响应式数据?
答案
Proxy 是 ES6 引入的元编程特性,可以拦截和自定义对象的基本操作。Reflect 是与 Proxy 配套的对象,提供了操作对象的标准方法。
Proxy 基础
// Proxy 语法
const proxy = new Proxy(target, handler);
// target: 被代理的目标对象
// handler: 包含拦截器(trap)的对象
基本示例
const user = {
name: 'Alice',
age: 25
};
const proxy = new Proxy(user, {
get(target, property, receiver) {
console.log(`读取属性: ${String(property)}`);
return target[property as keyof typeof target];
},
set(target, property, value, receiver) {
console.log(`设置属性: ${String(property)} = ${value}`);
(target as Record<string, unknown>)[property as string] = value;
return true;
}
});
proxy.name; // 读取属性: name → 'Alice'
proxy.age = 26; // 设置属性: age = 26
常用拦截器
get / set
interface User {
name: string;
age: number;
_password?: string;
}
const user: User = {
name: 'Alice',
age: 25,
_password: 'secret'
};
const proxy = new Proxy(user, {
// 拦截属性读取
get(target, property, receiver) {
// 私有属性不允许访问
if (String(property).startsWith('_')) {
throw new Error(`Cannot access private property: ${String(property)}`);
}
return Reflect.get(target, property, receiver);
},
// 拦截属性设置
set(target, property, value, receiver) {
// 类型检查
if (property === 'age' && typeof value !== 'number') {
throw new TypeError('Age must be a number');
}
// 范围检查
if (property === 'age' && (value < 0 || value > 150)) {
throw new RangeError('Age must be between 0 and 150');
}
return Reflect.set(target, property, value, receiver);
}
});
// proxy._password; // ❌ Error: Cannot access private property
proxy.age = 30; // ✅
// proxy.age = 'thirty'; // ❌ TypeError
// proxy.age = 200; // ❌ RangeError
has
const user = {
name: 'Alice',
_secret: 'hidden'
};
const proxy = new Proxy(user, {
// 拦截 in 操作符
has(target, property) {
if (String(property).startsWith('_')) {
return false; // 隐藏私有属性
}
return Reflect.has(target, property);
}
});
console.log('name' in proxy); // true
console.log('_secret' in proxy); // false
deleteProperty
const user = {
name: 'Alice',
age: 25,
id: 1
};
const proxy = new Proxy(user, {
// 拦截 delete 操作
deleteProperty(target, property) {
if (property === 'id') {
throw new Error('Cannot delete id property');
}
return Reflect.deleteProperty(target, property);
}
});
delete proxy.age; // ✅
// delete proxy.id; // ❌ Error
ownKeys
const user = {
name: 'Alice',
age: 25,
_password: 'secret',
_token: 'abc123'
};
const proxy = new Proxy(user, {
// 拦截 Object.keys、for...in 等
ownKeys(target) {
return Reflect.ownKeys(target).filter(
key => !String(key).startsWith('_')
);
}
});
console.log(Object.keys(proxy)); // ['name', 'age']
apply / construct
// 拦截函数调用
function sum(a: number, b: number): number {
return a + b;
}
const proxy = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`Calling with args: ${args}`);
return Reflect.apply(target, thisArg, args);
}
});
proxy(1, 2); // Calling with args: 1,2 → 3
// 拦截 new 操作
class User {
constructor(public name: string) {}
}
const UserProxy = new Proxy(User, {
construct(target, args, newTarget) {
console.log(`Creating user: ${args[0]}`);
return Reflect.construct(target, args, newTarget);
}
});
new UserProxy('Alice'); // Creating user: Alice
Reflect
Reflect 是一个内置对象,提供了拦截 JavaScript 操作的方法。
为什么使用 Reflect
// 1. 代替直接操作对象
// 旧方式
'name' in obj;
delete obj.name;
Object.keys(obj);
// 新方式(函数式)
Reflect.has(obj, 'name');
Reflect.deleteProperty(obj, 'name');
Reflect.ownKeys(obj);
// 2. 操作结果统一返回布尔值
// 旧方式(可能抛异常)
try {
Object.defineProperty(obj, 'name', { value: 'Alice' });
} catch (e) {
// 处理错误
}
// 新方式(返回布尔值)
if (Reflect.defineProperty(obj, 'name', { value: 'Alice' })) {
// 成功
} else {
// 失败
}
// 3. 与 Proxy 配合,正确处理 receiver
const proxy = new Proxy(obj, {
get(target, property, receiver) {
// 使用 Reflect 传递 receiver
return Reflect.get(target, property, receiver);
}
});
Reflect 方法
interface MyObject {
name: string;
greet(): string;
}
const obj: MyObject = { name: 'Alice', greet() { return `Hello, ${this.name}`; } };
// 属性操作
Reflect.get(obj, 'name'); // 'Alice'
Reflect.set(obj, 'name', 'Bob'); // true
Reflect.has(obj, 'name'); // true
Reflect.deleteProperty(obj, 'name'); // true
Reflect.ownKeys(obj); // ['name', 'greet']
// 函数操作
Reflect.apply(obj.greet, obj, []); // 'Hello, Alice'
Reflect.construct(Array, [1, 2, 3]); // [1, 2, 3]
// 原型操作
Reflect.getPrototypeOf(obj);
Reflect.setPrototypeOf(obj, null);
// 属性描述符
Reflect.defineProperty(obj, 'age', { value: 25 });
Reflect.getOwnPropertyDescriptor(obj, 'name');
// 可扩展性
Reflect.isExtensible(obj);
Reflect.preventExtensions(obj);
实际应用
响应式数据(Vue 3 原理)
type EffectFn = () => void;
// 依赖收集
const targetMap = new WeakMap<object, Map<string | symbol, Set<EffectFn>>>();
let activeEffect: EffectFn | null = null;
function track(target: object, key: string | symbol): void {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
function trigger(target: object, key: string | symbol): void {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
}
// 创建响应式对象
function reactive<T extends object>(target: T): T {
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 = Reflect.get(target, key, receiver);
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key);
}
return result;
}
});
}
// 副作用函数
function effect(fn: EffectFn): void {
activeEffect = fn;
fn();
activeEffect = null;
}
// 使用
const state = reactive({ count: 0, nested: { value: 1 } });
effect(() => {
console.log('Count:', state.count);
});
state.count++; // 自动输出: Count: 1
数据验证
interface Validator {
[key: string]: (value: unknown) => boolean;
}
function createValidatedObject<T extends object>(
target: T,
validators: Validator
): T {
return new Proxy(target, {
set(target, property, value, receiver) {
const validator = validators[property as string];
if (validator && !validator(value)) {
throw new TypeError(`Invalid value for ${String(property)}`);
}
return Reflect.set(target, property, value, receiver);
}
});
}
const user = createValidatedObject(
{ name: '', age: 0, email: '' },
{
name: (v) => typeof v === 'string' && (v as string).length > 0,
age: (v) => typeof v === 'number' && (v as number) >= 0,
email: (v) => typeof v === 'string' && (v as string).includes('@')
}
);
user.name = 'Alice'; // ✅
// user.name = ''; // ❌ TypeError
// user.age = -1; // ❌ TypeError
// user.email = 'invalid'; // ❌ TypeError
日志记录
function withLogging<T extends object>(target: T, name: string): T {
return new Proxy(target, {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (typeof value === 'function') {
return function(...args: unknown[]) {
console.log(`[${name}] Calling ${String(property)} with:`, args);
const result = value.apply(receiver, args);
console.log(`[${name}] ${String(property)} returned:`, result);
return result;
};
}
console.log(`[${name}] Getting ${String(property)}:`, value);
return value;
}
});
}
const calculator = withLogging(
{
add(a: number, b: number) { return a + b; },
value: 42
},
'Calculator'
);
calculator.value; // [Calculator] Getting value: 42
calculator.add(1, 2); // [Calculator] Calling add with: [1, 2]
// [Calculator] add returned: 3
负数索引数组
function createNegativeArray<T>(arr: T[]): T[] {
return new Proxy(arr, {
get(target, property, receiver) {
const index = Number(property);
if (!Number.isNaN(index) && index < 0) {
return Reflect.get(target, target.length + index, receiver);
}
return Reflect.get(target, property, receiver);
}
});
}
const arr = createNegativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
Proxy vs Object.defineProperty
| 特性 | Proxy | Object.defineProperty |
|---|---|---|
| 拦截操作 | 13种 | 仅 get/set |
| 数组监听 | ✅ 直接支持 | ❌ 需要重写方法 |
| 新增属性 | ✅ 自动拦截 | ❌ 需要手动定义 |
| 深层监听 | 惰性递归 | 初始化递归 |
| 性能 | 略慢 | 略快 |
| 兼容性 | ES6+ | ES5+ |
常见面试问题
Q1: Proxy 有哪些拦截操作?
答案:
Proxy 支持 13 种拦截操作:
| 拦截器 | 触发操作 |
|---|---|
| get | 读取属性 |
| set | 设置属性 |
| has | in 操作符 |
| deleteProperty | delete 操作符 |
| ownKeys | Object.keys 等 |
| apply | 函数调用 |
| construct | new 操作符 |
| getPrototypeOf | Object.getPrototypeOf |
| setPrototypeOf | Object.setPrototypeOf |
| isExtensible | Object.isExtensible |
| preventExtensions | Object.preventExtensions |
| defineProperty | Object.defineProperty |
| getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor |
Q2: 为什么 Vue 3 使用 Proxy 代替 Object.defineProperty?
答案:
- 数组监听:Proxy 可以直接监听数组变化,无需重写方法
- 新增属性:Proxy 可以监听新增属性,无需
Vue.set - 性能:惰性递归,只在访问时才代理嵌套对象(Object.defineProperty只能对「已存在的、具体的属性」做拦截,Proxy拦截的是「对这个对象的任何操作」,且只拦截一层,不会自动递归)
- 更多拦截:支持 has、deleteProperty 等操作
// Vue 2 的问题
const obj = { a: 1 };
obj.b = 2; // ❌ 无法检测
// Vue 3 使用 Proxy
const proxy = new Proxy(obj, handler);
proxy.b = 2; // ✅ 可以检测
Q3: Reflect 的作用是什么?
答案:
- 统一操作方式:将命令式操作变为函数式
- 返回布尔值:操作结果明确,不抛异常
- 配合 Proxy:正确处理 receiver 和 this 绑定
- 未来可扩展:新的对象操作方法都会加在 Reflect 上
Q4: 如何用 Proxy 实现私有属性?
答案:
function createPrivate<T extends object>(obj: T): T {
return new Proxy(obj, {
get(target, property) {
if (String(property).startsWith('_')) {
throw new Error('Private property');
}
return Reflect.get(target, property);
},
set(target, property, value) {
if (String(property).startsWith('_')) {
throw new Error('Private property');
}
return Reflect.set(target, property, value);
},
has(target, property) {
if (String(property).startsWith('_')) {
return false;
}
return Reflect.has(target, property);
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(
key => !String(key).startsWith('_')
);
}
});
}
Q5: Proxy 可以被撤销吗?
答案:
可以,使用 Proxy.revocable:
const { proxy, revoke } = Proxy.revocable({ name: 'Alice' }, {
get(target, property) {
return Reflect.get(target, property);
}
});
console.log(proxy.name); // 'Alice'
revoke(); // 撤销代理
// proxy.name; // ❌ TypeError: Cannot perform 'get' on a proxy that has been revoked