Proxy
是 ES6 引入的一个元编程(Metaprogramming)特性,可以对对象的基本操作进行拦截和自定义。它的出现大大增强了 JavaScript 的语言能力,让我们能够像"劫持"对象一样,对其行为进行精细控制。
Vue3 的响应式系统就是基于 Proxy
实现的,因此理解 Proxy 对于深入学习现代前端框架有重要意义。
一、Proxy 的基本语法
ini
const proxy = new Proxy(target, handler);
- target
被代理的对象(可以是对象、数组、函数甚至另一个 Proxy)。 - handler
包含捕捉器(trap)的对象,每个 trap 是一个函数,用于拦截目标对象的某种操作。
二、常见的捕捉器(Traps)
捕捉器 (Trap) | 说明 | 示例操作 |
---|---|---|
get | 拦截属性读取 | obj.prop 、obj["prop"] |
set | 拦截属性赋值 | obj.prop = 123 |
has | 拦截属性存在性 | "prop" in obj |
deleteProperty | 拦截属性删除 | delete obj.prop |
ownKeys | 拦截对象键枚举 | Object.keys(obj) 、for...in |
apply | 拦截函数调用 | fn(...args) |
construct | 拦截构造函数调用 | new Fn(...args) |
三、Reflect 与 Proxy 的关系
js
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log("读取属性", prop);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log("设置属性", prop, value);
return Reflect.set(target, prop, value, receiver);
}
});
四、Proxy 工作原理图示
flowchart LR
A[外部代码访问 obj.prop] --> B[Proxy 代理层]
B -->|触发 handler.get| C[自定义逻辑]
C -->|可修改/校验/记录日志| D[Reflect.get 调用目标对象]
D --> E[返回最终值给外部代码]
五、基础示例
1. 属性读取与设置拦截
js
const user = { name: "Alice", age: 20 };
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log(`读取属性:${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
if (prop === "age" && value < 0) {
throw new Error("年龄不能为负数!");
}
console.log(`设置属性:${prop} = ${value}`);
return Reflect.set(target, prop, value);
}
});
console.log(proxyUser.name); // "Alice"
proxyUser.age = 25;
2. 函数调用拦截
js
function sum(a, b) {
return a + b;
}
const proxySum = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`调用函数参数: ${args}`);
return Reflect.apply(target, thisArg, args) * 2;
}
});
console.log(proxySum(2, 3)); // 10
3. 构造函数拦截
js
class Person {
constructor(name) {
this.name = name;
}
}
const ProxyPerson = new Proxy(Person, {
construct(target, args) {
console.log("拦截 new 操作:", args);
return Reflect.construct(target, args);
}
});
const p = new ProxyPerson("Bob");
六、Proxy 的典型应用场景
1. 数据校验
在 set
捕捉器中拦截赋值,做合法性检查。
2. 默认值/容错
通过 get
捕捉器提供默认值,避免访问不存在属性时报错。
3. 日志与调试
统一在 get
/set
中打印日志,便于调试和监控。
4. 安全防护
隐藏敏感字段,或者禁止删除/修改关键属性。
5. 响应式系统
Vue3 的响应式就是通过 Proxy 拦截对象的 get
和 set
实现的。
6. Mock API / 虚拟对象
用 Proxy 生成动态对象,返回虚拟数据,常用于前端开发和测试。
应用场景脑图
mindmap
root((Proxy 应用场景))
数据校验
表单输入检查
参数合法性
默认值/容错
安全访问嵌套属性
避免 undefined 报错
日志与调试
调试器
调用记录
安全防护
隐藏敏感属性
防止非法修改
响应式系统
Vue3
MobX
Mock API
虚拟对象
自动返回值
七、Proxy vs Object.defineProperty
graph TB
subgraph Vue2: Object.defineProperty
A1[只能拦截已存在属性] --> A2[数组索引无法监听]
A2 --> A3[新增/删除属性无效]
end
subgraph Vue3: Proxy
B1[可拦截任意操作] --> B2[支持数组索引]
B2 --> B3[支持新增/删除属性]
B3 --> B4[覆盖更多场景: in/new/apply]
end
八、注意事项
- 性能开销比直接对象操作大。
- 代理对象和原对象不同引用。
- 部分内置对象(Map/Set)拦截不完整。
JSON.stringify(proxy)
会触发get
。
九、总结
- Proxy 是 JavaScript 的元编程能力,可拦截和自定义对象行为。
- 搭配 Reflect 更安全可靠。
- 在 Vue3、数据校验、安全防护等场景有广泛应用。
- 是对
Object.defineProperty
的升级,功能更强大,但需注意性能和兼容性。
十、完整案例:实现响应式对象
js
const bucket = new WeakMap();
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
if (activeEffect) {
let depsMap = bucket.get(obj);
if (!depsMap) bucket.set(obj, (depsMap = new Map()));
let deps = depsMap.get(key);
if (!deps) depsMap.set(key, (deps = new Set()));
deps.add(activeEffect);
}
return Reflect.get(obj, key);
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value);
const depsMap = bucket.get(obj);
if (depsMap) {
const effects = depsMap.get(key);
effects && effects.forEach(fn => fn());
}
return result;
}
});
}
const state = reactive({ count: 0 });
effect(() => {
console.log("count 发生变化:", state.count);
});
state.count++;
state.count++;
十一、响应式流程图
flowchart LR
A[访问 reactive 对象属性] --> B[Proxy get 捕捉器]
B -->|存在 activeEffect| C[收集依赖: 将 effect 存入 bucket]
A2[修改 reactive 对象属性] --> B2[Proxy set 捕捉器]
B2 --> D[查找对应依赖集合]
D --> E[触发副作用函数重新执行]
C --> F[副作用函数打印新值]
E --> F