一、Java 和 JavaScript 中的代理(Proxy)和反射(Reflection)有一些相似之处,但也有一些重要的异同点。
相似点:
- 代理(Proxy) :在 Java 和 JavaScript 中,代理都是一种机制,允许你在访问对象之前或之后执行一些额外的逻辑。代理模式可以用于实现拦截、修改、验证等功能。
- 反射(Reflection) :在 Java 和 JavaScript 中,反射都是一种机制,允许程序在运行时检查对象、类、方法等的信息,以及在运行时操作对象、类、方法等。这种能力使得程序能够动态地创建对象、调用方法、访问属性等。
异同点:
- 语言类型:
- Java 是一种静态类型语言,反射机制在 Java 中是由类
java.lang.reflect
包提供支持的。 - JavaScript 是一种动态类型语言,其反射机制是更为灵活的,并且内置在语言本身中,比如通过
Reflect
对象。
- 语法和用法:
- Java 中的代理和反射需要通过特定的语法和 API 来实现,比如使用
java.lang.reflect.Proxy
类创建代理对象,使用java.lang.reflect
包中的类来获取类信息、方法信息等。 - JavaScript 中的代理和反射则更为简单和直接,代理可以使用内置的
Proxy
对象来创建,而反射可以使用内置的Reflect
对象来获取和操作对象的信息。
- 权限和安全性:
- 在 Java 中,反射可能会涉及到对私有成员的访问,因此可能需要额外的权限配置。
- 在 JavaScript 中,代理和反射相对更加灵活,但也可能因此带来一些安全风险,比如可能会破坏对象的封装性。
- 性能:
- 由于 Java 是一种静态类型语言,其反射机制可能会带来一些性能开销,比如方法调用、类型转换等。
- JavaScript 中的代理和反射通常更为轻量级,因为 JavaScript 是一种动态类型语言,运行时信息更加灵活。
二、js中常见的代理
js
// 创建一个目标对象
const target = {
name: "Alice",
age: 30
};
// 创建一个代理对象
const handler = {
get(target, prop, receiver) {
// 在获取目标对象的属性时,添加一些额外的逻辑
console.log("Getting property:", prop);
// 返回目标对象的属性值
return target[prop];
}
};
// 使用 Proxy 对象创建代理
const proxy = new Proxy(target, handler);
// 通过代理对象访问目标对象的属性
console.log(proxy.name); // 输出: Getting property: name Alice
console.log(proxy.age); // 输出: Getting property: age 30
java中的代理(动态代理)示例
java
import java.lang.reflect.*;
// 定义一个接口
interface Subject {
void request();
}
// 创建一个目标类实现接口
class RealSubject implements Subject {
public void request() {
System.out.println("Real Subject: Handling request.");
}
}
// 创建一个代理类实现接口
class ProxyHandler implements InvocationHandler {
private Subject realSubject;
public ProxyHandler(Subject realSubject) {
this.realSubject = realSubject;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Proxy: Preprocessing.");
Object result = method.invoke(realSubject, args);
System.out.println("Proxy: Postprocessing.");
return result;
}
}
// 使用动态代理
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
InvocationHandler handler = new ProxyHandler(realSubject);
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler);
proxy.request();
}
}
三、js代理的一些注意点
3.1、Proxy.prototype是undefined
,不能使用instanceof。
3.2、捕获器不变式:防止捕获器定义出现过于反常的行为。比如一个数据属性是不可配置和不可写的属性,那么捕获器在返回时,是不能修改这个属性值的,否则抛TypeError。
3.3、代理是支持撤销的,前提是在创建代理时,使用如下方法:
js
// 创建一个目标对象
const target = {
name: "Alice",
age: 30
};
// 创建一个可撤销的代理对象
const { proxy, revoke } = Proxy.revocable(target, {
get(target, prop) {
console.log("Getting property:", prop);
return target[prop];
}
});
// 访问代理对象的属性
console.log(proxy.name); // 输出: Getting property: name Alice
console.log(proxy.age); // 输出: Getting property: age 30
// 撤销代理对象
revoke();
// 再次访问代理对象的属性将会抛出错误
try {
console.log(proxy.name);
} catch (error) {
console.error("Proxy has been revoked.");
}
3.4、反射的使用场景
1、反射api并不仅仅用于捕获处理程序,大多数反射的api在Object类型是有对应的方法,用于更细粒度的对象控制与操作。
2、状态标记,比如定义一个属性或者设置属性,至于是否成功,可以用反射api来重构,比try catch要好。
js
const o = {};
try {
Object.defineProperties(o, 'foo', 'bar');
console.log('success');
} catch (e) {
console.log('failure');
}
// 使用Reflect.defineProperty可以获取到状态标记是否定义成功
if(Reflect.defineProperty(o,'foo',{value:'bar'})){
console.log("success");
}else{
console.log("failure");
}
3、用一等函数替代操作符。
js
下面是一些常见的操作符和相应的 Reflect API 方法的对应关系:
赋值操作符 =:
操作符:obj[prop] = value
Reflect API:Reflect.set(obj, prop, value)
属性访问操作符 . 或 []:
操作符:obj[prop] 或 obj.prop
Reflect API:Reflect.get(obj, prop)
删除操作符 delete:
操作符:delete obj[prop]
Reflect API:Reflect.deleteProperty(obj, prop)
属性检测操作符 in:
操作符:prop in obj
Reflect API:Reflect.has(obj, prop)
原型访问操作符 instanceof:
操作符:obj instanceof Constructor
Reflect API:Reflect instanceof obj, Constructor
函数调用操作符 ():
操作符:func(...)
Reflect API:Reflect.apply(func, thisArg, args)
构造函数操作符 new:
操作符:new Constructor(...)
Reflect API:Reflect.construct(Constructor, args)
类型转换操作符 typeof:
操作符:typeof obj
Reflect API:Reflect.getPrototypeOf(obj)
4、安全的应用函数。
这里是说通过apply调用函数时,被调函数也重写了apply属性(可能性小),可以用以下方式解决安全问题。(函数也是对象,也可以被代理的)
js
Function.prototype.apply.call(myFunc,thisVal,argumentList);
Reflect.apply(myFunc,thisVal,argumentList);
3.5、代理嵌套也是可以的
js
const o = {
foo: 'bar'
};
const firstProxy = new Proxy(o, {
get(targe, value, receiver) {
console.log("first")
return Reflect.get(...arguments);
}
})
const secondProxy = new Proxy(firstProxy, {
get(targe, value, receiver) {
console.log("second")
return Reflect.get(...arguments);
}
})
console.log(secondProxy.foo);
3.6、代理的问题与不足
代理最大的问题是来源this
。目前Date类型代理有问题。
js
let vm = new WeakMap();
class User {
constructor(userid) {
vm.set(this, userid);
}
set id(userid) {
vm.set(this, userid);
}
get id() {
return vm.get(this);
}
}
const user = new User(123);
console.log(user.id);
const userProxy = new Proxy(user, {});
console.log(userProxy.id);// undefined;
因为代理里面this是userProxy,而vm种的this是user,导致取不到id值。
解决以上问题就是使用User取代user。
js
let vm = new WeakMap();
class User {
constructor(userid) {
vm.set(this, userid);
}
set id(userid) {
vm.set(this, userid);
}
get id() {
return vm.get(this);
}
}
const UserClassProxy = new Proxy(User, {});
const proxyUser = new UserClassProxy (456);
console.log(proxyUser.id);// 456
UserClassProxy
是一个代理对象,代理了 User
类。尽管 UserClassProxy
是一个代理对象,但是你仍然可以使用 new
关键字来实例化它,这样会触发代理对象的 construct
捕获器,从而模拟了目标对象的构造函数行为。
四、代理捕获器与反射方法
get/set/has() in操作符/defineProperty/getOwnPropertyDescriptor/deleteProperty/
ownKeys/getPrototypeof/setPrototypeof/isExtensible/preventExtensions/apply/construct/
明白一点:new是操作符,会触发constrct方法调用,代理可以传入类而不一定是类对象。apply传入的是Function对象。
五、代理模式常用的编程模式
跟踪属性访问、隐藏属性、属性验证、函数与构造函数参数验证、数据绑定与可观察对象。
js
// 函数参数验证代理
function createProxyFunc(func) {
return new Proxy(func, {
apply: function(target, thisArg, args) {
if (!args.every(arg => typeof arg === 'number')) {
throw new Error('参数必须为数字');
}
return Reflect.apply(target, thisArg, args);
}
});
}
const add = createProxyFunc(function(a, b) {
return a + b;
});
console.log(add(2, 3)); // 输出: 5
//console.log(add('2', 3)); // 抛出错误
// 构造函数参数验证代理
function createProxyConstructor(Constructor) {
return new Proxy(Constructor, {
construct: function(target, args) {
const [name, age] = args;
if (typeof name !== 'string' || typeof age !== 'number') {
throw new Error('参数类型不正确');
}
return Reflect.construct(target, args);
}
});
}
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
getInfo() {
return `姓名:${this.name},年龄:${this.age}`;
}
}
const ProxyUser = createProxyConstructor(User);
const user1 = new ProxyUser('Alice', 25);
console.log(user1.getInfo()); // 输出: 姓名:Alice,年龄:25
//const user2 = new ProxyUser(25, 'Alice'); // 抛出错误
js
const userList = [];
class User{
constructor(id){
this.id = id;
}
}
const proxy = new Proxy(User,{
construct(id){
const newUser = Reflect.construct(...arguments);
userList.push(newUser);
return newUser;
}
})
new proxy("11"); // new操作符会调用proxy的construct方法,Reflect.construct会调用User的构造。
new proxy("xxx");
console.log(userList);