什么是 Proxy
?
一句话概括:Proxy
是 JavaScript 中的一个“元编程”特性,它允许你创建一个对象的“代理”或“看门人”,从而可以拦截并自定义该对象上的基本操作(如读取、设置属性等)。
想象一下,你有一个很重要的对象 user
。
const user = {
name: "张三",
age: 30
};
正常情况下,你可以直接访问和修改它:
console.log(user.name);
// 读取
user.age = 31;
// 修改
而 Proxy
就像是在这个 user
对象前面安插了一个全能的门卫。从此以后,任何对 user
的访问和修改都必须先经过这个门卫。而你,作为门卫的制定者,可以规定门卫在每次操作发生时应该做什么。
Proxy
的核心三要素
创建一个代理需要三个东西:
target
(目标对象):就是你想要代理的那个原始对象(比如上面的user
)。handler
(处理器对象):一个配置对象,里面定义了当各种操作发生时,门卫(代理)应该执行的自定义行为。trap
(陷阱函数):handler
对象中的一个个方法,比如get
、set
等,它们就是用来“捕获”特定操作的“陷阱”。
一个简单的代码示例
让我们用代码来创建一个门卫。
// 1. 目标对象 (The VIP you want to protect)
const targetUser = {
name: "Alice",
age: 25
};
// 2. 处理器对象 (The rulebook for the doorman)
const handler = {
// 'get' 是一个陷阱 (trap),它会在读取属性时被触发
get(target, property) {
console.log(`有人正在读取属性 '${property}'...`);
// 必须返回原始值,否则读取操作会得到 undefined
return target[property];
},
// 'set' 是另一个陷阱,它会在设置属性时被触发
set(target, property, value) {
console.log(`有人正在设置属性 '${property}' 为 '${value}'...`);
// 我们可以在这里添加验证逻辑
if (property === 'age' && typeof value !== 'number') {
throw new TypeError("年龄必须是一个数字!");
}
// 执行原始的设置操作
target[property] = value;
// set 陷阱需要返回一个布尔值表示操作是否成功
return true;
}
};
// 3. 创建代理实例
const proxyUser = new Proxy(targetUser, handler);
// --- 现在,请注意:我们所有的操作都应该对 proxyUser 进行,而不是 targetUser ---
// 读取操作
console.log(proxyUser.name);
// 控制台输出:
// 有人正在读取属性 'name'...
// Alice
// 设置操作
proxyUser.age = 26;
// 控制台输出:
// 有人正在设置属性 'age' 为 '26'...
console.log(proxyUser.age);
// 控制台输出:
// 有人正在读取属性 'age'...
// 26
// 尝试一个非法的设置操作
try {
proxyUser.age = "二十七";
} catch (e) {
console.error(e.message);
}
// 控制台输出:
// 有人正在设置属性 'age' 为 '二十七'...
// 年龄必须是一个数字!
在这个例子中:
- 我们对
proxyUser
的任何读取都会被handler.get
捕获。 - 我们对
proxyUser
的任何写入都会被handler.set
捕获,并且我们还加入了验证逻辑。
Proxy
为何如此强大?(以及为什么 Vue 3 选择它)
Proxy
的强大之处在于它代理的是整个对象,而不是对象的某个属性。这解决了 Vue 2 中 Object.defineProperty
的核心痛点。
对比 Object.defineProperty
(Vue 2 的方式):
- 一次只能监听一个属性:你必须遍历对象的所有属性,为每个属性都调用一次
Object.defineProperty
。 - 无法监听新增/删除属性:如果给对象动态添加一个新属性,
Object.defineProperty
是不知道的,所以这个新属性不是响应式的。Vue 2 为此不得不提供了Vue.set
和Vue.delete
这样的补丁API。 - 无法直接监听数组索引和长度的变化:对数组通过索引修改(如
arr[0] = ...
)或修改length
属性,Object.defineProperty
同样无能为力。Vue 2 需要重写数组的push
,pop
,splice
等方法来hack式地实现监听。
Proxy
的优势 (Vue 3 的方式):
- 代理整个对象:
new Proxy(target, ...)
一行代码就代理了整个对象,性能更好,代码也更简洁。 - 天然支持新增/删除属性:因为代理的是整个对象,当你给代理对象添加或删除属性时,
get
/set
/deleteProperty
等陷阱会自然地被触发,无需任何额外API。 - 天然支持数组操作:对数组的任何索引访问、赋值、修改
length
属性,都会被Proxy
完美捕获。 - 提供更多陷阱:
Proxy
提供了多达13种陷阱,可以拦截各种各样的操作,远不止get
和set
。例如:has(target, prop)
:拦截prop in proxy
操作。deleteProperty(target, prop)
:拦截delete proxy[prop]
操作。apply(target, thisArg, args)
:拦截函数的调用。construct(target, args)
:拦截new proxy()
操作。
总结
Proxy
是 JavaScript 提供的一个底层元编程接口,它像一个强大的“拦截器”,让我们能够重写对象几乎所有的内部方法。
对于 Vue 3 来说,Proxy
是实现其响应式系统的完美工具。它让 Vue 能够:
- 高效地创建一个完全响应式的对象。
- 在
get
陷阱中收集依赖(谁读取了这个数据)。 - 在
set
或deleteProperty
等陷阱中触发更新(通知所有依赖它的地方进行更新)。
并且这一切都以一种更现代、更全面、更符合 JavaScript 语言直觉的方式完成,彻底告别了 Object.defineProperty
时代的各种限制和“补丁”。