携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
设计模式 - 代理模式(Proxy Design Pattern)
目的:
不改变原始类代码的情况下,通过引入代理类给原始类附加功能。
更好的使用服务,特别是在服务对象比较庞大,或者需要大量重复额外的处理。
命名方式
JavaScript中代理模式的具体表现形式就是ES6中的新增对象--- Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
对js中一切合理的对象的行为和属性进行自定义操作。对象在执行本身的方法或者获取对象本身属性时,其执行的过程和结果是你自定义的,而不是对象的。
语法:const proxy = new Proxy(target, handler)
Proxy方法需要传入两个参数,分别是需要被Proxy代理的对象和捕获对象行为的一系列捕获器传送门, 并返回一个新对象proxy。proxy拥有target的一切属性和方法。只是其行为与结果在handler中进行自定义操作。
在Proxy中共有13个捕获器,它们用于我们对对象、函数的方法调用监听。下面是Proxy捕获器以及它们的触发条件。
| 对象中的方法 | 对应触发条件 |
|---|---|
| handler.getPrototypeOf() | Object.getPrototypeOf 方法的捕捉器 |
| handler.setPrototypeOf() | Object.setPrototypeOf 方法的捕捉器 |
| handler.isExtensible() | Object.isExtensible 方法的捕捉器 |
| handler.preventExtensions() | Object.preventExtensions 方法的捕捉器 |
| handler.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor 方法的捕捉器。 |
| handler.defineProperty() | Object.defineProperty 方法的捕捉器 |
| handler.has() | in 操作符的捕捉器 |
| handler.get() | 属性读取操作的捕捉器handler.set()属性设置操作的捕捉器 |
| handler.deleteProperty() | delete 操作符的捕捉器 |
| handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器 |
| handler.apply() | 函数被apply调用操作的捕捉器 |
| handler.construct() | new 操作符的捕捉器 |
以get捕获器为例:
// 定义一个普通的对象
const obj = { name: "a" };
// 代理obj这个对象,并传入get捕获器
const objProxy = new Proxy(obj, {
// get捕获器
get: function (target, key) {
console.log(`捕获到对象获取${key}属性的值操作`);
if (key === 'age') {
return 18;
}
return target[key];
},
});
console.log(obj.name); // a
console.log(obj.age); // undefined
// 通过代理对象操作obj对象
console.log(objProxy.name); // a
console.log(objProxy.age); // 18
适用场景
什么时候需要使用代理模式?
缓存代理
将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算
const cache = {};
const getData = (function() {
return function(url) {
if (cache[url]) {
return Promise.resolve(cache[url]);
}
return get(url).then((res) => {
cache[url] = res;
return res;
}).catch(err => console.error(err))
}
})();
getData('/getData'); // 发起http请求
getData('/getData'); // 返回缓存数据
const cache = {};
const getMoreData = (url) => {
return get(url).then((res) => {
return res;
}).catch(err => console.error(err))
}
}
function getMoreDataCacheProxy(fn){
return new Proxy(fn, {
apply: (target, context, args)=> {
const argString = args.join(' ');
if(cache.has(argString)) {
return cache[argString];
}
let res = Reflect.apply(target, context, args);
cache.set(argString, res);
return res;
}
});
}
// 使用
let proxy = getMoreDataCacheProxy(getMoreData);
let res1 = proxy('/getData'); // 发起http请求
let res2 = proxy('/getData'); // 返回缓存数据
保护代理
保护代理用于控制不同权限的对象对目标对象的访问
class Car {
drive()
{ return "driving"; };
}
class CarProxy {
constructor(driver) {
this.driver = driver;
}
drive() {
// 保护代理,仅18岁才能开车
return (this.driver.age < 18) ? "too young to drive" : new Car().drive();
};
}
const carProxy = new Proxy(Car, {
get: function (target, key) {
console.log(`捕获到对象获取${key}属性的值操作`);
if (key == 'age' && target[key] < 16) {
return "too young to drive";
}
return Reflect.apply(target, drive());
},
});
更多的场景:类私有属性的保护(不可以随意获取/修改私有属性的值)。 赋值验证校验。
虚拟代理
访问一个需要消耗大量资源的对象
图片加载
export default class ImageProxy extends React.Component {
render() {
return <View>
<Image source={loading.png} style={this.state.isLoad ? {展示} : {隐藏}} />
<Image uri={{uri: this.props.uri}} onLoad={() => {
this.setState({
isLoad: false
})
}}/>
</View>
}
}
每个客户端都创建一个访问数据库的对象,导致查询很缓慢。
通过代理类,将代理对象更新给所有客户端,这样客户端访问代理对象后,代理对象会创建实际访问数据库对象,进行处理。
附加说明
代理与继承的区分。继承的方式会直接继承父类的所有方法,达不到按需控制访问权限的灵活效果,代理比继承更加灵活