代理模式
1) 什么是代理模式?
官网说明: 为一个对象提供一个代用品或占位符,以便控制对它的访问。
通俗语言: 就是原有对象上的方法和属性不方便直接操作, 需要通过另一个对象通过转发请求的方式来操作原有对象。
2) 为什么使用代理模式?
生活中买东西例子:
去厂家买东西当然是最便宜的, 但是直接去买非常不方便, 比如有路程远、 是否有货、售后一系列问题。 所以我们可以去代理商那里买, 虽然贵了点,但是省去了一些列的麻烦。买家无需关心厂家生产过程, 只关关心自己是否能买到。
对应到代码也是一样使用代理模式会多一些代码, 但是日后更易维护。并且用户不介意操作的到底是原对象还是代理对象只要能达到目的就可以。
代理模式带来的好处:
- 可以减少没必要的性能开销
- 通过代理能遵循开放封闭原则
代理模式的好处分析
1) 减少没必要的开销:
我去买厂家的东西, 但是厂家的商品不一定有库存, 我没有必要每次跑一趟去看看有没有货(没必要的开销), 但是代理商和厂家关系密切, 有货了他第一个知道, 我可以去代理商那里买。
// 当有了商品的时候我才去执行
let my = function () {
console.log('走去购买');
}
// 中间商 可以监听到工厂是否有生产商品
let center = {
listenHasGood(fac) {
fac.create(my)
}
}
// 工厂
let fac = {
count: 0,
create(cb) {
// 假设两秒后生产了商品
setTimeout(() => {
this.count = 1;
cb();
}, 2000);
}
}
// 代理商监听然后通知即可
center.listenHasGood(fac);
2) 通过代理实现开放封闭原则:
例如一个添加图片的方法, 如果想给它添加一个预加载功能则可以使用代理模式, 在不修改原方法的情况下添加新功能, 实现了单一职责原则
// 1. 原始方法生成一个图片
let createImg = (function () {
let img = document.createElement('img');
document.body.append(img);
return function (src) {
img.src = src;
}
})();
createImg('..线上图片地址') // 图片过大或者网速慢的时候会导致白屏
// 2. 通过代理实现图片预加载
function proxyImg(src) {
createImg('./src/本地预加载图片.jpg');
let img = new Image();
// 当这个临时图片加载完毕后说明远程图片已经下载完毕,此时可以赋值给预加载图片
img.onload = function () {
createImg(this.src);
}
img.src = src;
}
proxyImg('..线上图片地址')
代理重要原则: 与本体接口的一致性
还是图片预加载功能为例, 如果某一天我们不需要预加载了, 那就不需要通过代理请求, 直接使用本体请求即可.
上述代码中代理和本体都是相同方法,只要传入url路径即可, 所以可以直接替换
用户看来代理和本体是一致的, 虽然不知道内部的区别但都能获取相同的结果, 这样设计的好处:
- 用户可以放心地请求代理,他只关心是否能得到想要的结果
- 可根据需求直接互换代理和本体
代理带来的性能优化 ( 实用性案例 )
1) 缓存计算数据:
如果某些程序需要大量计算, 可以通过代理把之前的计算结果缓存起来, 如果相同的计算直接返回缓存结果
乘积案例
// 乘积函数
let mult = function(){
console.log('进入计算');
let a = 1;
for ( let i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
// 两次都要计算
mult( 2, 3 );
mult( 2, 3 );
// 给乘积加上代理
let proxyMult = (function () {
let cache = {};
return function () {
// 将每次的形参转为字符串
let arg = [].join.call(arguments, ',');
// 如果相同直接返回缓存
if (arg in cache) {
return cache[arg];
}
return cache[arg] = mult.apply(null, arguments);
}
})();
// 只会计算一次
proxyMult(2, 3);
proxyMult(2, 3);
2) 减少http请求, 节流优化 :
假设有一个日记功能 , 在一个input中每一次输入都要传递给后台进行保存, 如果对实时性要求不高可以延缓1秒钟,然后把1秒钟内输入的结果全部传递给后台 ( 也就是节流函数 )
// 假设这是一个请求
let ajax = function (params) {
console.log('发送请求参数是: ' + params);
}
// 通过代理减少了请求次数
let proxyAjax = (function () {
let cache = [], // 记录每一次输入的值
timer;
return function (inpVal) {
cache.push(inpVal);
// 如果延时器还在则不执行
if (timer) {
return;
}
timer = setTimeout(() => {
console.log(cache);
ajax(cache.join(','));
timer = null;
cache = [];
}, 1000);
}
})();
// input输入框
let inp = document.getElementById('inp');
inp.addEventListener('input', (e) => {
proxyAjax(e.target.value);
}, false);
3) 实现惰性加载- 节约没有必要的性能开销:
假设我设置的某些任务依赖jquery这个库, 但是这些任务又是在特点的条件下才执行,比如按F2键. 所以没有必要一开始就去加载jquery. 等到需要执行的时候才去加载然后执行任务
let proxyJquery = (function () {
// 任务缓存列表
let cacheFnList = [];
return {
// 添加依赖jquery的方法
add(fn) {
cacheFnList.push(fn)
},
// 加载jquery
listenLoad(e) {
if (e.keyCode === 113) {
let script = document.createElement('script');
script.onload = function () {
for (let i = 0; i < cacheFnList.length; i++) {
cacheFnList[i]();
}
}
script.src = 'http://libs.baidu.com/jquery/2.0.0/jquery.min.js';
document.getElementsByTagName('head')[0].append(script);
}
}
}
})();
// 订阅相关任务
proxyJquery.add(function () {
// 通过jq获取body标签
console.log($('body'));
})
proxyJquery.add(function () {
// 通过jq获取id app标签
console.log($('#app'));
})
// 监听用户是否触发
document.addEventListener('keydown', proxyJquery.listenLoad, false);
总结
代理模式也是前端必须要掌握的一项模式, 因为它和很多设计模式息息相关.
虽然代理非常有用, 但是使用前还是要考虑清楚, 如同买东西的案例, 先考虑一下我们直接访问原对象是否方便, 如果不方便代理是很好的选择, 如果方便那么代理模式就是锦上添花了.