JavaScript的代理(Proxy)与反射(Reflect)API是ES6引入的重要特性,二者配合可以实现对对象底层操作的拦截和自定义,是元编程能力的核心体现。除了基础的对象属性读写拦截,它们在很多复杂场景下有非常实用的高级应用。

代理与反射API的基础回顾
代理对象是通过Proxy构造函数创建的,接收一个目标对象和一个处理器对象,处理器对象中可以定义各种陷阱(trap)来拦截对目标对象的操作。反射API则是Reflect对象上的一系列方法,这些方法与代理的陷阱一一对应,用于执行对象的默认操作。
基础的代理使用示例:
// 基础代理示例
const target = { name: "test" };
const handler = {
get(target, key, receiver) {
console.log(`访问属性 ${key}`);
return Reflect.get(target, key, receiver);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:访问属性 name 然后输出 test
高级应用场景一:对象属性校验
通过代理的set陷阱,可以在设置对象属性时自动进行校验,避免无效数据被写入对象。比如我们可以定义一个只允许写入数字类型的年龄属性的对象:
// 对象属性校验示例
const person = {};
const personProxy = new Proxy(person, {
set(target, key, value, receiver) {
if (key === "age") {
if (typeof value !== "number" || value < 0 || value > 150) {
throw new TypeError("age 必须是 0 到 150 之间的数字");
}
}
return Reflect.set(target, key, value, receiver);
}
});
personProxy.age = 20; // 正常设置
console.log(personProxy.age); // 输出 20
personProxy.age = "二十"; // 抛出 TypeError 错误
高级应用场景二:对象访问统计
我们可以通过代理记录对象属性的访问次数和修改次数,用于性能分析或者操作审计。这种方式不需要修改目标对象的原有逻辑,通过代理层即可完成统计:
// 对象访问统计示例
const data = { a: 1, b: 2 };
const accessRecord = {
getCount: {},
setCount: {}
};
const dataProxy = new Proxy(data, {
get(target, key, receiver) {
if (!accessRecord.getCount[key]) {
accessRecord.getCount[key] = 0;
}
accessRecord.getCount[key]++;
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if (!accessRecord.setCount[key]) {
accessRecord.setCount[key] = 0;
}
accessRecord.setCount[key]++;
return Reflect.set(target, key, value, receiver);
}
});
dataProxy.a;
dataProxy.a;
dataProxy.b = 3;
console.log(accessRecord); // 输出 { getCount: { a: 2, b: 1 }, setCount: { b: 1 } }
高级应用场景三:实现不可变对象
有些场景下我们需要对象一旦创建就不能被修改,通过代理的set、deleteProperty等陷阱可以拦截所有修改操作,返回只读的不可变对象:
// 不可变对象实现示例
function createImmutableObj(obj) {
return new Proxy(obj, {
set() {
throw new Error("不可变对象不允许修改属性");
},
deleteProperty() {
throw new Error("不可变对象不允许删除属性");
},
// 拦截对对象原型的修改
setPrototypeOf() {
throw new Error("不可变对象不允许修改原型");
}
});
}
const immutableData = createImmutableObj({ x: 10, y: 20 });
console.log(immutableData.x); // 输出 10
immutableData.x = 30; // 抛出错误
高级应用场景四:依赖收集(响应式核心逻辑)
前端响应式框架的核心依赖收集逻辑,很多都是通过代理和反射API实现的。当对象属性被访问时收集依赖,当属性被修改时触发依赖更新:
// 简易依赖收集示例
const depMap = new Map(); // 存储属性对应的依赖函数列表
function track(key, effect) {
if (!depMap.has(key)) {
depMap.set(key, []);
}
depMap.get(key).push(effect);
}
function trigger(key) {
const effects = depMap.get(key);
if (effects) {
effects.forEach(effect => effect());
}
}
const state = { count: 0 };
const reactiveState = new Proxy(state, {
get(target, key, receiver) {
// 这里假设当前正在执行的副作用函数存在 window.activeEffect 中,实际框架中会有更严谨的处理
if (window.activeEffect) {
track(key, window.activeEffect);
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(key); // 属性修改后触发依赖更新
return result;
}
});
// 定义副作用函数
window.activeEffect = () => {
console.log(`count 最新值:${reactiveState.count}`);
};
// 首次执行触发依赖收集
window.activeEffect();
// 修改属性触发更新
reactiveState.count = 1; // 输出 count 最新值:1
反射API的核心作用
很多开发者会疑惑为什么拦截后要用Reflect的方法执行默认操作,而不是直接操作目标对象。主要有几个原因:
- Reflect的方法返回值更符合预期,比如
Reflect.set返回布尔值表示操作是否成功,而直接赋值在某些场景下不会返回状态 - Reflect的方法可以接收
receiver参数,保证当目标对象有getter/setter,且this指向代理对象时,逻辑不会出错 - Reflect的API与代理的陷阱一一对应,语义更清晰,代码可读性更高
使用注意事项
代理和反射API虽然强大,但也有一些需要注意的点:
- 代理只能拦截对代理对象的操作,直接操作目标对象不会被拦截
- 代理的性能比直接操作对象略低,不要在高频操作的场景下过度使用
- 部分内置对象的内部插槽无法通过代理拦截,比如
Date、Map等对象的某些操作可能不符合预期
代理和反射API是JavaScript高级开发的重要工具,合理运用可以解决很多常规方式难以处理的对象操作问题,提升代码的灵活性和可扩展性。
JavaScriptProxyReflect元编程对象拦截修改时间:2026-06-18 09:51:39