定义
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式的关键是当客户不方便直接访问对象或者不满足需要时。提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象
st=>start: 客户
op=>operation: 代理对象
e=>end: 本体对象
st->op->e
应用场景
- 在前后端分离的架构中,收到浏览器端 同源策略 的影响,常见的问题就是 CORS(跨域资源共享),这个可以通过后端设置响应头
Access-Control-Allow-Origin
为*
,来允许浏览器跨域访问资源。
当然,前端也可以通过 node 以及其中间件,在本地搭建一个服务,然后将页面中的请求转发到真正的 web server(同源策略只针对浏览器,服务端向服务端发送请求是没有同源限制的)。在后者的方式中。node 承担的就是一个代理的作用。 - 当需要中间人来辅助完成工作,或锦上添花时使用。
小明追女神
代理需要在一些额外的场景下才能发挥它的作用,不然只会把原来简单的事情搞复杂。比如小明去追女神。如果没有其他影响因素,单纯的通过小红去转送花,那是毫无意义的。
但是,我们假设,当女神心情好的时候收到花,小明表白的成功率会提升很多,而当女神心情差的时候收到花,成功率近乎于 0。
那么小明在没有和女神过多的接触下。无法分辨女神的情绪。所以可以通过和女神比较熟悉的小红,小红来监听女神的心情。当女神心情好的时候再送出花。这样成功率会更高。
var Flower = function () {};
var xiaoming = {
sendFlower: function (target) {
var flower = new Flower();
target.receiveFlower(flower);
},
};
// 代理
var xiaohong = {
receiveFlower: function (flower) {
// 监听女神的心情
goddess.listenGoodMood(function () {
goddess.receiveFlower(flower);
});
},
};
var goddess = {
receiveFlower: function (flower) {
console.log("收到花:" + flower);
},
listenGoodMood: function (fn) {
// 假设女神大概10秒后心情变好。
setTimeout(function () {
fn();
}, 10000);
},
};
xiaoming.sendFlower(xiaohong);
保护代理
一束花的开销可能很大,所以我们需要当真正需要它的时候才去创建。
var xiaohong = {
receiveFlower: function () {
// 监听女神的心情
goddess.listenGoodMood(function () {
// 延迟创建flower对象
var flower = new Flower();
goddess.receiveFlower(flower);
});
},
};
缓存代理
在项目中我们经常遇到一些分页请求,或者一些历史数据、历史图表数据请求等等。理论上,我们可以将已经看过的数据缓存下来。
下面我们就按照这两个,使用高阶函数动态创建代理。
分页数据
// 假设项目中已有一个请求库,且只返回具体数据
const request = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: {
"content-type": "application/json",
},
})
.then((response) => response.json())
.then((data) => data.data)
.cacth(() => {});
};
const getData = (() => {
// 缓存
const cache = new Map();
return async function (pageNum, pageSize) {
if (cache.has(`${pageSize}-${pageNum}`)) {
return new Pormise((resolve) => {
resolve(cache.get(`${pageSize}-${pageNum}`));
});
}
return request("POST", "some api", { pageNum, pageSize }).then((data) => {
// 设置缓存
cache.set(`${pageSize}-${pageNum}`, data);
return data;
});
};
})();
// 当用户翻页时调用
const data = await getData(1, 10);
也可以使用 Proxy 构造函数来创建代理,该代理可以优化一些耗时操作
function cacheProxy(fn) {
const cache = new Map();
return new Proxy(fn, {
// 函数调用trap
apply(target, context, args) {
const argsProp = args.join(" ");
// Reflect提供的静态方法和Object上的对应方法相同
if (Reflect.has(cache, argsProp)) {
console.log("using cache data...");
return Reflect.get(cache, argsProp);
}
console.log("cal new data...");
const result = target(...args);
Reflect.set(cache, argsProp, result);
return result;
},
});
}
const expensiveHandler = (a, b) => {
return a + b;
};
const expensiveProxy = cacheProxy(expensiveHandler);
expensiveProxy(1, 2); // cal new data...
expensiveProxy(1, 2); // using cache data...
上面获取历史数据的例子我们也可以用 Proxy 来改写一下
const createCacheProxy = (fn) => {
const cache = new Map();
return new Proxy(fn, {
async apply(target, context, args) {
const argsProp = args.join(" ");
if (Reflect.has(cache, argsProp)) {
return Reflect.get(cache, argsProp);
}
const result = await target(...args);
Reflect.set(cache, argsProp, result);
return result;
},
});
};
const getData = async (pageNum, pageSize) => {
const data = await request("POST", "some api", { pageNum, pageSize });
return data;
};
const getDataProxy = createCacheProxy(getData);
const data = await getDataProxy(1, 10);