跳到主要内容

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

特性ProxyObject.defineProperty
拦截操作13种仅 get/set
数组监听✅ 直接支持❌ 需要重写方法
新增属性✅ 自动拦截❌ 需要手动定义
深层监听惰性递归初始化递归
性能略慢略快
兼容性ES6+ES5+

常见面试问题

Q1: Proxy 有哪些拦截操作?

答案

Proxy 支持 13 种拦截操作:

拦截器触发操作
get读取属性
set设置属性
hasin 操作符
deletePropertydelete 操作符
ownKeysObject.keys 等
apply函数调用
constructnew 操作符
getPrototypeOfObject.getPrototypeOf
setPrototypeOfObject.setPrototypeOf
isExtensibleObject.isExtensible
preventExtensionsObject.preventExtensions
definePropertyObject.defineProperty
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor

Q2: 为什么 Vue 3 使用 Proxy 代替 Object.defineProperty?

答案

  1. 数组监听:Proxy 可以直接监听数组变化,无需重写方法
  2. 新增属性:Proxy 可以监听新增属性,无需 Vue.set
  3. 性能:惰性递归,只在访问时才代理嵌套对象(Object.defineProperty只能对「已存在的、具体的属性」做拦截,Proxy拦截的是「对这个对象的任何操作」,且只拦截一层,不会自动递归)
  4. 更多拦截:支持 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 的作用是什么?

答案

  1. 统一操作方式:将命令式操作变为函数式
  2. 返回布尔值:操作结果明确,不抛异常
  3. 配合 Proxy:正确处理 receiver 和 this 绑定
  4. 未来可扩展:新的对象操作方法都会加在 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

相关链接