代理与反射
Proxy 与 Reflect 用于拦截并自定义对象基本操作,是 Vue 3 等响应式系统的底层机制之一。
一、Proxy:拦截与自定义操作
Proxy 用于创建一个对象的代理,从而可以拦截和重新定义该对象的基本操作(如属性读取、赋值、函数调用等)。
1. 基本语法
const proxy = new Proxy(target, handler);
target:被代理的目标对象(可以是普通对象、数组、函数、甚至另一个代理)。handler:定义拦截行为的对象,内部包含捕获器(trap)方法。
2. 常用捕获器
| 捕获器 | 触发时机 |
|---|---|
get(target, prop, receiver) | 读取属性:proxy[prop]、Object.create 等 |
set(target, prop, value, receiver) | 设置属性:proxy[prop] = value |
has(target, prop) | in 运算符:prop in proxy |
deleteProperty(target, prop) | delete proxy[prop] |
apply(target, thisArg, argsList) | 函数调用:proxy(...args)、call/apply |
construct(target, argsList, newTarget) | new proxy(...args) |
ownKeys(target) | Object.getOwnPropertyNames、Object.keys 等 |
getOwnPropertyDescriptor(target, prop) | Object.getOwnPropertyDescriptor |
defineProperty(target, prop, descriptor) | Object.defineProperty |
preventExtensions(target) | Object.preventExtensions |
isExtensible(target) | Object.isExtensible |
getPrototypeOf(target) | Object.getPrototypeOf、__proto__、instanceof |
setPrototypeOf(target, proto) | Object.setPrototypeOf |
3. 示例:get 和 set 拦截
const user = { name: "Alice", age: 30 };
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`GET ${String(prop)}`);
return prop in target ? target[prop] : "default";
},
set(target, prop, value, receiver) {
console.log(`SET ${String(prop)} = ${value}`);
target[prop] = value;
return true; // 表示设置成功
},
});
console.log(proxy.name); // GET name → Alice
proxy.age = 31; // SET age = 31
console.log(proxy.xxx); // GET xxx → 'default'
4. 可撤销 Proxy
使用 Proxy.revocable() 创建一个可以手动撤销的代理。撤销后任何操作都会抛出 TypeError。
const target = { msg: "hello" };
const { proxy, revoke } = Proxy.revocable(target, {});
console.log(proxy.msg); // 'hello'
revoke();
// console.log(proxy.msg); // TypeError: Cannot perform 'get' on a proxy that has been revoked
二、Reflect:反射 API
Reflect 是一个内置对象,提供了一组与 Proxy 捕获器一一对应的静态方法。它的主要用途是与 Proxy 协同工作,实现默认行为。
1. 常用 Reflect 方法
Proxy 捕获器 | 对应的 Reflect 方法 |
|---|---|
get | Reflect.get(target, prop, receiver) |
set | Reflect.set(target, prop, value, receiver) |
has | Reflect.has(target, prop) |
deleteProperty | Reflect.deleteProperty(target, prop) |
apply | Reflect.apply(target, thisArg, argsList) |
construct | Reflect.construct(target, argsList, newTarget) |
ownKeys | Reflect.ownKeys(target) |
| … 共 13 个方法 | 与 Proxy 捕获器完全对应 |
2. 与 Proxy 协同:保持默认行为
在 Proxy 的捕获器中,通常需要调用 Reflect 的方法来完成默认操作,同时可以添加额外逻辑。
const proxy = new Proxy(target, {
set(target, prop, value, receiver) {
// 自定义验证
if (prop === "age" && (value < 0 || value > 150)) {
throw new Error("Invalid age");
}
// 调用 Reflect 执行默认赋值
return Reflect.set(target, prop, value, receiver);
},
});
3. 替代 Object 部分方法的现代 API
Reflect 的方法相比于 Object 同名方法(如 defineProperty、getPrototypeOf)有以下优势:
- 返回布尔值而不是抛出异常(例如
Reflect.defineProperty返回true/false,而Object.defineProperty失败时抛错)。 - 更合理的 this 绑定:
Reflect.get可以传入receiver来正确绑定 getter 中的this。 - 与
Proxy保持一致,更符合函数式风格。
// 旧方式(容易抛异常)
try {
Object.defineProperty(obj, "prop", { value: 1 });
} catch (e) {}
// 新方式(返回值更可控)
if (Reflect.defineProperty(obj, "prop", { value: 1 })) {
// 成功
} else {
// 失败
}
三、应用场景
1. 属性验证(数据校验)
在 set 捕获器中对赋值进行校验。
const user = {};
const validatedUser = new Proxy(user, {
set(target, prop, value) {
if (prop === "age" && (typeof value !== "number" || value < 0)) {
throw new TypeError("Age must be a positive number");
}
target[prop] = value;
return true;
},
});
validatedUser.age = 25; // OK
// validatedUser.age = -5; // Error
2. 数据绑定与响应式系统(Vue 3 核心原理)
Vue 3 的响应式基于 Proxy,能够拦截对对象的所有访问和修改,并触发依赖更新。
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 收集依赖(track)
console.log(`GET ${String(key)}`, result);
return typeof result === "object" ? reactive(result) : result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 触发更新(trigger)
if (oldValue !== value) {
console.log(`SET ${String(key)}: ${oldValue} -> ${value}`);
}
return result;
},
};
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });
state.count++; // GET count → SET count: 0 -> 1
3. 日志记录与性能测试
记录属性访问、函数调用耗时等。
function performanceMonitor(fn) {
return new Proxy(fn, {
apply(target, thisArg, args) {
console.time("fn");
const result = Reflect.apply(target, thisArg, args);
console.timeEnd("fn");
return result;
},
});
}
const slowFunc = n => {
let sum = 0;
for (let i = 0; i < n; i++) sum += i;
return sum;
};
const monitored = performanceMonitor(slowFunc);
monitored(1e7); // 输出执行时间
4. 隐藏私有属性(模拟私有字段)
拦截 get 和 has,阻止以 _ 开头的属性被外部访问。
const privateProxy = obj =>
new Proxy(obj, {
get(target, prop) {
if (prop.startsWith("_")) {
throw new Error("Private property");
}
return Reflect.get(target, prop);
},
has(target, prop) {
if (prop.startsWith("_")) return false;
return Reflect.has(target, prop);
},
});
const data = { _secret: 123, public: 456 };
const pData = privateProxy(data);
console.log(pData.public); // 456
// console.log(pData._secret); // Error
5. 默认属性值(“虚拟”属性)
拦截 get,当属性不存在时返回默认值,而不实际添加该属性。
const withDefault = (obj, defaultVal) =>
new Proxy(obj, {
get(target, prop) {
return prop in target ? target[prop] : defaultVal;
},
});
const person = { name: "Bob" };
const p = withDefault(person, "unknown");
console.log(p.name); // 'Bob'
console.log(p.age); // 'unknown'
6. 数组负索引支持(语法糖)
const createArray = arr =>
new Proxy(arr, {
get(target, prop) {
const index = Number(prop);
if (!isNaN(index) && index < 0) {
return target[target.length + index];
}
return Reflect.get(target, prop);
},
});
const arr = createArray([1, 2, 3, 4]);
console.log(arr[-1]); // 4
总结
| 概念 | 核心要点 |
|---|---|
Proxy | 拦截对象底层操作,提供 13 种捕获器;Proxy.revocable 创建可撤销代理。 |
Reflect | 与 Proxy 一一对应的静态方法,用于实现默认行为;更安全、函数式的替代 Object 某些方法。 |
| 应用场景 | 数据校验、响应式系统(Vue 3)、日志/性能、隐藏私有属性、默认值、数组负索引等。 |
Proxy 配合 Reflect 赋予了 JavaScript 强大的元编程能力,使得我们可以安全、灵活地控制对象行为,是现代框架和库的重要基础。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| MDN:Proxy | 代理 |
| MDN:Reflect | 反射 API |
| ECMA-262:Proxy Objects | 规范(英文) |
相关文章
错误处理与调试
结构化错误类型、try/catch 与全局未捕获处理是健壮应用的基础。
浏览器相关 API
DOM 事件、存储、fetch、Worker、Observer 等由浏览器提供的 Web API(非 ECMAScript 核心)。HTML 结构见 标签。
性能优化
防抖/节流、渲染与内存、资源预加载及引擎行为是前端性能的主要抓手。相关 CSS 见 视觉效果、Head 资源提示。
模块化与工程化
ESM、CommonJS 与打包工具(Tree Shaking、代码分割)是前端工程化的基础。入门见 JavaScript 基础 · 模块。
执行模型与作用域
执行上下文与词法作用域决定变量在何处可访问、闭包如何形成,以及 var / let / const 的差异。语言概览见 JavaScript 基础。
数据结构与集合增强
Map / Set、WeakMap / WeakSet、数组高阶方法、TypedArray 与 Symbol 等扩展了内置数据结构能力。见 语言基础。