JavaScript中的代理(Proxy)与反射(Reflect)
代理(Proxy)
Proxy是ES6引入的一个强大特性,它允许你创建一个对象的代理,从而可以拦截和自定义该对象的基本操作。
基本语法
const proxy = new Proxy(target, handler);
target: 要代理的目标对象handler: 一个对象,其属性是定义代理行为的函数
常见拦截操作
const handler = {
// 拦截属性读取
get(target, prop, receiver) {
console.log(`Getting property ${prop}`);
return Reflect.get(...arguments);
},
// 拦截属性设置
set(target, prop, value, receiver) {
console.log(`Setting property ${prop} to ${value}`);
return Reflect.set(...arguments);
},
// 拦截in操作符
has(target, prop) {
console.log(`Checking if property ${prop} exists`);
return Reflect.has(...arguments);
},
// 拦截delete操作
deleteProperty(target, prop) {
console.log(`Deleting property ${prop}`);
return Reflect.deleteProperty(...arguments);
},
// 拦截函数调用
apply(target, thisArg, argumentsList) {
console.log(`Calling function with args: ${argumentsList}`);
return Reflect.apply(...arguments);
},
// 拦截new操作符
construct(target, argumentsList, newTarget) {
console.log(`Constructing with args: ${argumentsList}`);
return Reflect.construct(...arguments);
}
};
使用场景
-
验证和过滤
const validator = { set(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('Age must be an integer'); } if (value < 0 || value > 150) { throw new RangeError('Age must be between 0 and 150'); } } obj[prop] = value; return true; } }; const person = new Proxy({}, validator); person.age = 100; // OK person.age = 'young'; // 抛出TypeError -
数据绑定和观察
function observe(obj, callback) { return new Proxy(obj, { set(target, prop, value) { callback(prop, target[prop], value); target[prop] = value; return true; } }); } const observed = observe({}, (prop, oldVal, newVal) => { console.log(`Property ${prop} changed from ${oldVal} to ${newVal}`); }); -
实现私有属性
const handler = { get(target, prop) { if (prop.startsWith('_')) { throw new Error('Attempt to access private property'); } return target[prop]; }, set(target, prop, value) { if (prop.startsWith('_')) { throw new Error('Attempt to modify private property'); } target[prop] = value; return true; } }; const obj = new Proxy({ _secret: 42, public: 10 }, handler);
反射(Reflect)
Reflect是一个内置对象,它提供了拦截JavaScript操作的方法。这些方法与Proxy handler的方法一一对应。
主要方法
Reflect.apply(target, thisArg, args) // 调用函数
Reflect.construct(target, args) // 相当于new target(...args)
Reflect.get(target, property, receiver) // 获取属性值
Reflect.set(target, property, value, receiver) // 设置属性值
Reflect.defineProperty(target, property, descriptor) // 定义属性
Reflect.deleteProperty(target, property) // 删除属性
Reflect.has(target, property) // 检查属性是否存在
Reflect.ownKeys(target) // 获取所有自身属性键
Reflect.getPrototypeOf(target) // 获取原型
Reflect.setPrototypeOf(target, prototype) // 设置原型
使用场景
-
更优雅的函数调用
// 传统方式 Function.prototype.apply.call(Math.max, null, [1, 2, 3]); // Reflect方式 Reflect.apply(Math.max, null, [1, 2, 3]); -
属性操作
const obj = { a: 1 }; // 传统方式 'a' in obj; delete obj.a; // Reflect方式 Reflect.has(obj, 'a'); Reflect.deleteProperty(obj, 'a'); -
与Proxy配合使用
const proxy = new Proxy({}, { get(target, prop, receiver) { console.log(`Getting ${prop}`); return Reflect.get(target, prop, receiver); } });
代理与反射的关系
- Proxy的handler方法和Reflect方法一一对应
- Reflect方法通常用于在Proxy handler中实现默认行为
- Reflect方法提供了更规范的API来操作对象
实际应用示例
实现简单的ORM
class Model {
constructor(data) {
return new Proxy(this, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
if (data[prop] !== undefined) {
return data[prop];
}
return undefined;
},
set(target, prop, value) {
if (prop in target) {
target[prop] = value;
} else {
data[prop] = value;
}
return true;
}
});
}
}
class User extends Model {}
const user = new User({ name: 'John', age: 30 });
console.log(user.name); // John
user.age = 31;
console.log(user.age); // 31
实现不可变对象
function createImmutableObject(obj) {
return new Proxy(obj, {
get(target, prop) {
const value = Reflect.get(target, prop);
return typeof value === 'object' && value !== null
? createImmutableObject(value)
: value;
},
set() {
throw new Error('Cannot modify an immutable object');
},
deleteProperty() {
throw new Error('Cannot delete from an immutable object');
},
defineProperty() {
throw new Error('Cannot define property on an immutable object');
}
});
}
const immutable = createImmutableObject({ a: 1, b: { c: 2 } });
immutable.a = 2; // Error
immutable.b.c = 3; // Error
注意事项
- Proxy只能代理对象,不能代理原始值
- Proxy的handler必须返回正确的值类型,否则会抛出TypeError
- 不是所有操作都可以被拦截,例如
typeof、instanceof等 - 性能考虑:Proxy操作比直接对象操作要慢,在性能敏感的场景要谨慎使用