定义与概念
JavaScript中的Proxy与Reflect是ES6中引入的新特性,它们可以帮助我们更高效地控制对象。
代理(Proxy)是一种设计模式,它允许我们在访问对象的同时,添加一些额外的操作。代理对象与被代理对象实现相同的接口,代理对象会接受并处理所有对被代理对象的访问请求。
代理是对象通过一个代理对象来控制对原对象的读取、设置、调用及其他操作,并对这些操作进行预处理或附加操作,主要用于拦截对象
反射(Reflection)是指程序可以在运行时获取、操作、修改它本身的状态或行为。反射是一种动态获取类型信息和操作类型的能力,可以在运行时动态调用类中的方法或属性。
反射可以使我们知晓(获取)对象详情,操控对象的成员(属性),在调用对象的方法时加入额外的逻辑,主要用于操作对象
在Java中,反射与代理可以通过reflect以及其中的Proxy类与InvocationHandler接口实现代理,通过reflect实现反射;而在C++中则是用继承和虚函数实现代理模式,使用模板和元编程修改检查结构达到反射。代理和反射通常都是编译时的概念,在编译阶段就已经确定了代理和反射的具体实现方式
而JS则是在运行时动态地创建代理和使用反射,并且提供了类似的概念和实现:全局的Proxy与Reflect对象
属性及函数
Proxy
使用new Proxy实例化时需要传入以下两个参数
- target:被代理的目标对象。
- handler:定义代理行为的对象。
handler参数中有一些钩子函数,在代理对象发生变化时会触发:
- get:在读取代理对象的属性时
- set:在对代理对象的属性进行赋值时
- has:在使用 in 运算符检查代理对象是否具有某个属性时
- deleteProperty:在删除代理对象的属性时
- apply:当前代理对象为函数,在调用代理对象时
- construct:在使用 new 运算符创建代理对象的实例时
- getOwnPropertyDescriptor:在获取代理对象的属性描述符时
- defineProperty:在定义代理对象的属性时
- getPrototypeOf:在获取代理对象的原型时
- setPrototypeOf:在设置代理对象的原型时
- isExtensible:在检查代理对象是否可扩展时
- preventExtensions:在防止代理对象的扩展时
- ownKeys:在返回代理对象的所有键时
Reflect
Reflect对象中的函数与上述handler中的钩子函数是一一对应的
- get:用于读取一个对象的属性值
- set:用于设置一个对象的属性值
- has:用于判断一个对象是否有某个属性
- deleteProperty:用于删除一个对象的属性
- apply:当前代理对象为函数,用于调用当前对象的方法
- construct:用于通过构造函数创建一个新的对象实例
- getOwnPropertyDescriptor:用于读取一个对象的自身属性描述对象
- defineProperty: 用于为一个对象定义一个属性
- getPrototypeOf:用于读取一个对象的原型对象
- setPrototypeOf:用于设置一个对象的原型对象
- isExtensible:用于判断一个对象是否可扩展
- preventExtensions: 用于防止一个对象被扩展
- ownKeys:用于读取一个对象的所有自身属性的键名
使用场景
以下代码可以触发所有的钩子函数
const proxyFactory = (target, opts) => {
return new Proxy(target, {
set: (target, propertyKey, value, receiver) => {
// console.log(target, propertyKey, value, receiver);
console.log("执行了set");
return Reflect.set(target, propertyKey, value);
},
get: (target, property, receiver) => {
// console.log(target, property, receiver);
console.log("执行了get");
return Reflect.get(target, property, receiver);
},
has: (target, property) => {
// console.log(target, property);
console.log("执行了has");
return Reflect.has(target, property);
},
deleteProperty: (target, property) => {
// console.log(target, property);
console.log("执行了deleteProperty");
return Reflect.deleteProperty(target, property);
},
apply: (target, thisArg, argumentsList) => {
// console.log(target, thisArg, argumentsList);
console.log("执行了apply");
return Reflect.apply(target, thisArg, argumentsList);
},
construct: (target, argumentsList, newTarget) => {
// console.log(target, argumentsList, newTarget);
console.log("执行了construct");
return Reflect.construct(target, argumentsList, newTarget);
},
getOwnPropertyDescriptor: (target, property) => {
// console.log(target, property);
console.log("执行了getOwnPropertyDescriptor");
return Reflect.getOwnPropertyDescriptor(target, property);
},
defineProperty: (target, property, descriptor) => {
// console.log(target, property, descriptor);
console.log("执行了defineProperty");
return Reflect.defineProperty(target, property, descriptor);
},
getPrototypeOf: (target) => {
// console.log(target);
console.log("执行了getPrototypeOf");
return Reflect.getPrototypeOf(target);
},
setPrototypeOf: (target, prototype) => {
// console.log(target, prototype);
console.log("执行了setPrototypeOf");
return Reflect.setPrototypeOf(target, prototype);
},
isExtensible: (target) => {
// console.log(target);
console.log("执行了isExtensible");
return Reflect.isExtensible(target);
},
preventExtensions: (target) => {
// console.log(target);
console.log("执行了preventExtensions");
return Reflect.preventExtensions(target);
},
ownKeys: (target) => {
// console.log(target);
console.log("执行了ownKeys");
return Reflect.ownKeys(target);
},
...opts,
});
};
const obj = {
name: "张三",
age: 20,
};
const fn = function () {
return "hello";
};
const __obj = proxyFactory(obj);
const __fn = proxyFactory(fn); // apply只有当当前代理对象为函数时才会执行
const init = () => {
// set;
__obj.name = "李四";
// get;
__obj.name;
// has;
"name" in __obj;
// deleteProperty;
delete __obj.age;
// apply;
__fn();
// construct;
new __fn();
// getOwnPropertyDescriptor;
Object.getOwnPropertyDescriptor(__obj, "name");
// defineProperty;
Object.defineProperty(__obj, "name", {
value: "王五",
});
// getPrototypeOf;
Object.getPrototypeOf(__obj);
// setPrototypeOf;
Object.setPrototypeOf(__obj, null);
// isExtensible;
Object.isExtensible(__obj);
// preventExtensions;
Object.preventExtensions(__obj);
// ownKeys;
Object.getOwnPropertyNames(__obj);
console.log(__obj, obj);
};
init();
如何实现
实现过程
了解了代理和反射的概念和用法,我们可以尝试使用ES5的语法实现一下对象属性的增删改查
首先是反射
var __Reflect = {
set(target, prop, value) {
target[prop] = value;
},
get(target, prop) {
return target[prop];
},
defineProperty(target, property, descriptor) {
return Object.defineProperty(target, property, descriptor);
},
deleteProperty(target, property) {
return delete target[property];
},
};
然后是代理
function __Proxy(target, handler) {
var __target = {};
this.target = target;
this.handler = handler;
this.init(__target);
return __target;
}
__Proxy.prototype = {
init(__target) {
this.readWrite(__target);
Object.__defineProperty = this.defineProperty.bind(this);
Object.__delete = this.deleteProperty.bind(this);
},
readWrite(__target) {
// 初始化读写函数
var target = this.target;
var handler = this.handler;
for (const key in target) {
Object.defineProperty(__target, key, {
configurable: true,
set(val) {
// 新增/修改
target[key] = val;
return handler.set(target, key, val);
},
get() {
// 读取
return handler.get(target, key);
},
});
}
},
defineProperty(target, property, descriptor) {
// 定义/修改
var __d = this.handler.defineProperty;
var fn = typeof __d === "function" ? __d : __Reflect.defineProperty; // 如果钩子函数存在,则执行代理拦截函数
return fn(target, property, descriptor);
},
deleteProperty(target, property) {
// 删除
var __delete = this.handler.deleteProperty;
if (typeof __delete === "function") {
return __delete(target, property); // 如果钩子函数存在,则执行代理拦截函数
}
return __Reflect.deleteProperty(target, property);
},
};
大致介绍一下思路:我的做法是在执行属性读写,对象定义,删除前对属性进行函数拦截,将结果抛出,这里的delete由于是全局关键字执行,所以我在Object中增加了删除属性的函数用于模拟delete操作,此外还重写了一下defineProperty函数,用作对象劫持
运行效果
var obj = {
name: "张三",
age: 20,
};
var proxy = new __Proxy(obj, {
set(target, prop, value) {
console.log("set");
return __Reflect.set(target, prop, value);
},
get(target, prop) {
console.log("get");
return __Reflect.get(target, prop);
},
defineProperty(target, property, descriptor) {
console.log("defineProperty");
return __Reflect.defineProperty(target, property, descriptor);
},
deleteProperty(target, property) {
console.log("deleteProperty");
return __Reflect.deleteProperty(target, property);
},
});
// set
proxy.name = "李四";
// get
console.log(proxy.name);
// defineProperty
Object.__defineProperty(proxy, "name", {
value: "王五",
});// 使用自定义的defineProperty,劫持对象操作
// delete
Object.__delete(proxy, "name");// 这个函数是模拟delete关键字的操作
console.log(proxy.name, obj.name);
应用限制及优点
限制:
- 上面我们也说了代理和反射是ES6新增的两个对象,兼容性上会有一些折扣;
- 此外使用反射操作对象和直接操作对象还是有区别的,使用反射操作对象会有性能的损失;
优点:
- 提高了对象的灵活性,监听对象及操作对象变得简易
- 拓展性变高了,可以应对更多针对对象的操作