JavaScript高级程序设计笔记 9
第9章 代理与反射 — 快速复习笔记
1. 代理(Proxy)
1.1 基本概念
- 代理:拦截并自定义对象的基本操作(如属性查找、赋值、枚举、函数调用等)。
- 创建:
let proxy = new Proxy(target, handler)target:目标对象handler:捕获器(trap)对象,定义拦截行为
1.2 捕获器(Handler Traps)
| 捕获器 | 对应操作 |
|---|---|
get(target, prop, receiver) |
读取属性:proxy[prop] |
set(target, prop, value, receiver) |
赋值属性:proxy[prop] = value |
has(target, prop) |
in 操作符:prop in proxy |
deleteProperty(target, prop) |
delete proxy[prop] |
ownKeys(target) |
Object.keys(proxy) 等 |
getOwnPropertyDescriptor(target, prop) |
Object.getOwnPropertyDescriptor(proxy, prop) |
defineProperty(target, prop, descriptor) |
Object.defineProperty(proxy, prop, descriptor) |
preventExtensions(target) |
Object.preventExtensions(proxy) |
isExtensible(target) |
Object.isExtensible(proxy) |
getPrototypeOf(target) |
Object.getPrototypeOf(proxy) |
setPrototypeOf(target, proto) |
Object.setPrototypeOf(proxy, proto) |
apply(target, thisArg, args) |
函数调用:proxy(...args) |
construct(target, args, newTarget) |
new 操作符:new proxy(...args) |
1.3 简单示例
let target = { name: 'target' };
let handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return prop === 'name' ? 'proxied' : Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
let proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出"Getting name" → "proxied"
proxy.age = 20; // 输出"Setting age = 20"
2. 反射(Reflect)
2.1 基本概念
- Reflect 是一个内置对象,提供与捕获器一一对应的方法。
- 所有
Reflect方法都有返回值(如set返回布尔值,defineProperty返回布尔值),便于捕获器中返回。 - 通常用法:在捕获器中调用
Reflect的对应方法,保持默认行为。
2.2 Reflect 常用方法
| Reflect 方法 | 对应操作 | 返回值 |
|---|---|---|
Reflect.get(target, prop, receiver) |
读取属性 | 属性值 |
Reflect.set(target, prop, value, receiver) |
设置属性 | 布尔值(是否成功) |
Reflect.has(target, prop) |
in 操作符 |
布尔值 |
Reflect.deleteProperty(target, prop) |
delete |
布尔值 |
Reflect.ownKeys(target) |
返回自身属性键数组 | 数组 |
Reflect.construct(target, args, newTarget) |
new 操作符 |
实例对象 |
2.3 使用 Reflect 的典型模式
let proxy = new Proxy(target, {
get(target, prop, receiver) {
// 自定义逻辑
console.log('get', prop);
// 调用默认行为
return Reflect.get(target, prop, receiver);
}
});
3. 代理的特性与限制
3.1 代理的局限性
- 代理对象与目标对象不是同一个对象,严格相等
===不成立。 - 某些内部槽(如
Date、Map、Promise等)无法通过代理完全模拟,因为原生对象依赖内部[[Slot]]。 - 代理无法拦截某些不可配置或不可写的属性(会静默失败或抛出错误)。
3.2 可撤销代理
let { proxy, revoke } = Proxy.revocable(target, handler);
revoke(); // 撤销代理,之后任何操作都会抛出 TypeError
4. 代理的应用场景
4.1 数据验证/类型校验
let validator = {
set(target, prop, value) {
if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
throw new TypeError('age must be positive number');
}
return Reflect.set(target, prop, value);
}
};
let person = new Proxy({}, validator);
person.age = 25; // OK
person.age = -1; // TypeError
4.2 日志记录
function logAccess(target) {
return new Proxy(target, {
get(target, prop) {
console.log(`Property ${prop} accessed`);
return Reflect.get(target, prop);
}
});
}
4.3 实现私有属性(命名约定但可拦截)
let privateData = new WeakMap();
class Person {
constructor(name) {
privateData.set(this, { name });
return new Proxy(this, {
get(target, prop) {
if (prop === 'name') return privateData.get(target).name;
return Reflect.get(target, prop);
}
});
}
}
4.4 性能测试(函数调用拦截)
function measure(fn) {
return new Proxy(fn, {
apply(target, thisArg, args) {
console.time('call');
let result = Reflect.apply(target, thisArg, args);
console.timeEnd('call');
return result;
}
});
}
4.5 自动填充对象属性(默认值)
function defaultValue(target, defaultVal) {
return new Proxy(target, {
get(target, prop) {
return prop in target ? Reflect.get(target, prop) : defaultVal;
}
});
}
let obj = defaultValue({}, 0);
console.log(obj.x); // 0
5. Reflect 的额外用途
- 替代
Object上某些命令式方法,并返回更有意义的布尔值(例如Reflect.defineProperty返回布尔值,而Object.defineProperty失败时抛出错误)。 - 将函数调用优雅地转化为函数式风格:
Reflect.apply(fn, thisArg, args)等同于fn.apply(thisArg, args)。
6. 常见面试题速查
-
Proxy 和 Object.defineProperty 有什么不同?
→ Proxy 可以拦截更多操作(如in、delete、函数调用等),并且可以代理整个对象(包括数组、动态属性),而defineProperty只能拦截属性读写。 -
为什么通常需要在捕获器中调用 Reflect 方法?
→ 保持默认行为;同时Reflect返回恰当的布尔值,让捕获器知道操作是否成功。 -
Proxy 能代理嵌套对象吗?
→ 不能自动深层代理,需要递归包装(即给子对象也创建 Proxy)。 -
什么是可撤销代理?
→ 使用Proxy.revocable()创建,之后通过revoke()函数禁用代理,所有操作抛出错误。 -
代理的
this指向问题?
→ 代理对象调用方法时,方法内部的this指向代理对象,而非目标对象。可以通过Reflect.get的receiver参数控制。 -
如何拦截函数调用?
→ 使用apply捕获器,目标必须是函数。 -
Reflect 相比 Object 方法的优势?
→ 返回值更友好(布尔值而非抛出错误),可作为函数调用(如Reflect.defineProperty),统一行为。 -
代理可以拦截
Symbol属性吗?
→ 可以,Symbol也是有效的属性键,捕获器同样适用。 -
什么情况下代理会失效或报错?
→ 对不可配置、不可写的属性进行不符合规范的拦截时会抛出TypeError;撤销代理后所有操作抛出TypeError。 -
Proxy 的性能如何?
→ 相比原生操作有一定开销,但合理使用对大多数应用影响不大。避免在热路径上大量使用。

浙公网安备 33010602011771号