在 JavaScript 中,Proxy
是一个强大的原生对象,引入于 ES6(ECMAScript 2015),它允许你创建一个代理对象,用来拦截和自定义对目标对象的操作(如属性访问、赋值、函数调用等)。简单来说,Proxy
就像一个“中间人”,在你和目标对象之间加了一层,可以让你控制对象的行为。
什么是 Proxy
?
Proxy
是一个构造函数,通过它可以创建一个代理对象。它的基本语法是:
1 |
const proxy = new Proxy(target, handler); |
target
:被代理的目标对象,可以是对象、数组、函数等。handler
:一个对象,定义了拦截行为的方法(称为“陷阱”,traps),比如get
、set
、has
等。proxy
:返回的代理对象,替代了直接操作target
。
通过 handler
定义的陷阱,你可以自定义目标对象的行为,比如:
- 读取属性时做什么(
get
) - 设置属性时做什么(
set
) - 检查属性是否存在时做什么(
has
)
怎么理解 Proxy
?
你可以把 Proxy
想象成一个“门卫”或者“过滤器”:
- 没有
Proxy
时,你直接操作对象,就像直接走进房间拿东西。 - 有了
Proxy
,相当于房间门口多了个门卫,每次你想拿东西(访问属性)或放东西(赋值),门卫都会先检查、记录,甚至可以阻止你。
这个“门卫”不会改变原始对象,而是通过代理对象提供一个可控的接口。
类比示例:
假设有一个普通对象:
1 2 3 |
const person = { name: "Alice", age: 25 }; console.log(person.name); // "Alice" person.age = 26; // 直接改 |
如果加了 Proxy
,你可以控制访问和修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const person = { name: "Alice", age: 25 }; const proxyPerson = new Proxy(person, { get(target, prop) { console.log(`正在访问 ${prop}`); return target[prop]; }, set(target, prop, value) { console.log(`正在设置 ${prop} 为 ${value}`); target[prop] = value; return true; // 表示设置成功 } }); console.log(proxyPerson.name); // 输出: "正在访问 name",然后 "Alice" proxyPerson.age = 26; // 输出: "正在设置 age 为 26" |
怎么用 Proxy
?
Proxy
的用法是通过定义 handler
中的陷阱来实现自定义行为。以下是一些常见用法和示例:
1. 拦截属性访问(get
)
- 用途:监控属性读取,或者为不存在的属性提供默认值。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
const obj = { name: "Bob" }; const proxy = new Proxy(obj, { get(target, prop) { if (prop in target) { return target[prop]; } return "属性不存在"; } }); console.log(proxy.name); // "Bob" console.log(proxy.age); // "属性不存在" |
2. 拦截属性赋值(set
)
- 用途:验证赋值的数据,或者禁止某些操作。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const obj = { age: 20 }; const proxy = new Proxy(obj, { set(target, prop, value) { if (prop === "age" && typeof value !== "number") { throw new Error("年龄必须是数字!"); } target[prop] = value; return true; // 表示成功 } }); proxy.age = 21; // 成功 console.log(obj.age); // 21 proxy.age = "twenty"; // 抛出错误: "年龄必须是数字!" |
3. 拦截属性检查(has
)
- 用途:自定义
in
操作符的行为。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
const obj = { secret: "hidden" }; const proxy = new Proxy(obj, { has(target, prop) { if (prop === "secret") { return false; // 隐藏 secret 属性 } return prop in target; } }); console.log("secret" in proxy); // false console.log("secret" in obj); // true(原始对象不受影响) |
4. 拦截函数调用(apply
)
- 用途:代理函数,添加额外的逻辑。
- 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function greet(name) { return `Hello, ${name}!`; } const proxyFn = new Proxy(greet, { apply(target, thisArg, args) { console.log("函数被调用,参数是:", args); return target.apply(thisArg, args).toUpperCase(); } }); console.log(proxyFn("Alice")); // 输出: // "函数被调用,参数是: ["Alice"]" // "HELLO, ALICE!" |
常用的陷阱(Traps)
Proxy
支持多种陷阱,这里列出几个常用的:
get(target, prop, receiver)
:拦截属性读取。set(target, prop, value, receiver)
:拦截属性赋值。has(target, prop)
:拦截in
操作。deleteProperty(target, prop)
:拦截delete
操作。apply(target, thisArg, args)
:拦截函数调用。construct(target, args)
:拦截new
操作。
完整列表可以在 MDN Proxy 文档 查看。
在 Vue 3 中的应用
Vue 3 的响应式系统大量依赖 Proxy
。它通过代理对象来追踪数据的变化:
- 当你访问一个响应式对象的属性时(
get
),Vue 收集依赖。 - 当你修改属性时(
set
),Vue 触发更新。
例如:
1 2 3 4 5 |
import { reactive } from 'vue'; const state = reactive({ count: 0 }); state.count; // 触发 get,收集依赖 state.count = 1; // 触发 set,通知更新 |
Vue 用 Proxy
替代了 Vue 2 的 Object.defineProperty
,因为 Proxy
可以拦截更多操作(如数组方法、动态属性),性能和功能更强大。
注意事项
- 不可代理私有字段:
Proxy
无法拦截以#
开头的私有字段,因为它们不走 getter/setter。 - 性能开销:代理会增加一些计算开销,不适合对性能敏感的场景。
- 原始对象保持不变:
Proxy
不会修改target
,只是提供了一个代理层。
总结
- 是什么:
Proxy
是一个拦截对象操作的工具。 - 怎么理解:像一个可编程的中间层,控制对目标对象的访问。
- 怎么用:通过
new Proxy(target, handler)
创建,用陷阱自定义行为。