ES6 Proxy 和 Reflect 用法整理
Proxy 代理
Proxy 用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
基本语法
const proxy = new Proxy(target, handler)
target:要代理的目标对象handler:包含陷阱(trap)的对象,用于定义拦截行为
常用陷阱(handler 方法)
1. 属性访问拦截
const handler = { get(target, property, receiver) { console.log(`获取属性 ${property}`); return Reflect.get(...arguments); // 默认行为 } };
2. 属性设置拦截
const handler = { set(target, property, value, receiver) { console.log(`设置属性 ${property} = ${value}`); return Reflect.set(...arguments); // 返回布尔值表示是否成功 } };
3. 删除属性拦截
const handler = { deleteProperty(target, property) { console.log(`删除属性 ${property}`); return Reflect.deleteProperty(...arguments); } };
4. 函数调用拦截
const handler = { apply(target, thisArg, argumentsList) { console.log(`函数被调用`); return Reflect.apply(...arguments); } };
5. 构造函数拦截
const handler = { construct(target, argumentsList, newTarget) { console.log(`构造函数被调用`); return Reflect.construct(...arguments); } };
6. 其他陷阱
has(target, prop):拦截in操作符ownKeys(target):拦截Object.keys()等操作getPrototypeOf(target):拦截获取原型setPrototypeOf(target, proto):拦截设置原型isExtensible(target):拦截Object.isExtensible()preventExtensions(target):拦截Object.preventExtensions()getOwnPropertyDescriptor(target, prop):拦截Object.getOwnPropertyDescriptor()defineProperty(target, prop, descriptor):拦截Object.defineProperty()
完整示例
const target = { name: "target", value: 42 }; const handler = { get(target, prop, receiver) { if (prop === 'value') { return target[prop] * 2; } return Reflect.get(...arguments); }, set(target, prop, value, receiver) { if (prop === 'value') { target[prop] = value * 2; return true; } return Reflect.set(...arguments); } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // "target" console.log(proxy.value); // 84 proxy.value = 10; console.log(proxy.value); // 40 (10 * 2 * 2)
Reflect 反射
Reflect 是一个内置对象,提供了拦截 JavaScript 操作的方法,与 Proxy 配合使用。
常用方法
1. 属性操作
// 获取属性 Reflect.get(target, propertyKey[, receiver]) // 设置属性 Reflect.set(target, propertyKey, value[, receiver]) // 删除属性 Reflect.deleteProperty(target, propertyKey) // 检查属性是否存在 Reflect.has(target, propertyKey)
2. 对象原型
// 获取原型 Reflect.getPrototypeOf(target) // 设置原型 Reflect.setPrototypeOf(target, prototype)
3. 对象扩展性
// 检查是否可扩展 Reflect.isExtensible(target) // 防止扩展 Reflect.preventExtensions(target)
4. 属性描述符
// 获取属性描述符 Reflect.getOwnPropertyDescriptor(target, propertyKey) // 定义属性 Reflect.defineProperty(target, propertyKey, attributes) // 获取自身属性键 Reflect.ownKeys(target)
5. 函数调用
// 调用函数 Reflect.apply(target, thisArgument, argumentsList) // 构造函数调用 Reflect.construct(target, argumentsList[, newTarget])
Reflect 与 Proxy 配合示例
const loggedObj = new Proxy({}, { get(target, name) { console.log(`get ${name}`); return Reflect.get(target, name); }, set(target, name, value) { console.log(`set ${name} = ${value}`); return Reflect.set(target, name, value); }, deleteProperty(target, name) { console.log(`delete ${name}`); return Reflect.deleteProperty(target, name); } }); loggedObj.x = 1; // set x = 1 console.log(loggedObj.x); // get x, 然后 1 delete loggedObj.x; // delete x
Proxy 和 Reflect 的实际应用
1. 观察者模式
const createObservable = (target) => { const listeners = new Set(); const handler = { set(target, prop, value) { const oldValue = target[prop]; const result = Reflect.set(target, prop, value); if (oldValue !== value) { listeners.forEach(listener => listener(prop, oldValue, value)); } return result; }, // 其他陷阱... }; const proxy = new Proxy(target, handler); return { proxy, subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); } }; }; const { proxy, subscribe } = createObservable({ count: 0 }); const unsubscribe = subscribe((prop, oldValue, newValue) => { console.log(`属性 ${prop} 从 ${oldValue} 变为 ${newValue}`); }); proxy.count++; // 属性 count 从 0 变为 1 unsubscribe();
2. 验证属性
const validator = { set(target, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('年龄必须是整数'); } if (value < 0 || value > 150) { throw new RangeError('年龄必须在0-150之间'); } } return Reflect.set(...arguments); } }; const person = new Proxy({}, validator); person.age = 30; // 正常 person.age = '30'; // TypeError: 年龄必须是整数
3. 负索引数组
function createNegativeArrayProxy(array) { return new Proxy(array, { get(target, index) { index = Number(index); return index < 0 ? target[target.length + index] : target[index]; }, set(target, index, value) { index = Number(index); if (index < 0) { target[target.length + index] = value; } else { target[index] = value; } return true; } }); } const arr = createNegativeArrayProxy([1, 2, 3, 4]); console.log(arr[-1]); // 4 arr[-2] = 10; console.log(arr); // [1, 2, 10, 4]
注意事项
- Proxy 和 Reflect 是 ES6 新增的特性,旧浏览器可能需要 polyfill
- Proxy 可能会影响性能,因为每次操作都会经过陷阱
- 某些操作(如
prototype操作)在某些情况下可能无法被 Proxy 完全拦截 - 使用 Reflect 可以保持默认行为,同时方便在陷阱中添加自定义逻辑
Proxy 和 Reflect 结合使用可以创建强大的元编程能力,但也要谨慎使用,避免过度复杂化代码。