设计模式——代理模式
代理模式是业务场景中很常用的模式。关键在于当不方便直接访问一个对象时,提供一个替身对象来接受访问,然后通过替身对象的内部处理将请求转交给本体对象。
图片预加载示例
在前端开发中,图片预加载是一个很常见的需求。如果直接给img标签设置src属性,当图片过多或者网络不通畅时,网页渲染时图片可能还未完全加载,则图片位置就会显示空白。为了增强用户体验,常见做法是用一张loading图片占位然后异步加载填充。下面我们先实现一个常见的预加载函数。
let myImage = (function() {
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);
let img = new Image;
img.onload = function() {
imgNode.src = img.src
};
return {
setSrc: function(src) {
imgNode.src = '...' // loading图片路径
img.src = src;
}
}
})();
虽然实现了功能,但这段代码存在一些问题。MyImage对象不仅负责给img节点设置值,还负责预加载功能。由于两个功能产生了强耦合导致拓展性不足。如果以后可能某些图片极小或者网速变得很快,不需要预加载功能了就必须深入内部修改逻辑。接下来我们用代理模式来优化这段代码。
let myImage = (function() {
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
let proxy = (function() {
let img = new Image;
img.onload = function() {
myImage.setSrc(this.src);
};
return {
setSrc: function(src) {
myImage.setSrc(...); // loading图片路径
img.src = src;
}
}
}) ();
现在我们可以通过proxy代理对象来控制对本体对象的图片加载功能的访问了,并且将本体的预加载图片功能设置到了代理对象上。我们可以看到,虽然增加了代理对象但是并没有改变本体对象的接口。也就意味着如果今后不需要预加载,我们只需要更改用户请求接口到本体对象即可,整个过程对用户是不可见的。至此,我们就实现了代理模式的封装与隔离。
虚拟代理
代理模式根据场景不同也分为很多种。在前端开发中常用的代理有虚拟代理和缓存代理。以上图片预加载实现的代理模式就是虚拟代理。虚拟代理的原则就是节省请求的开支,这一点和函数节流的思想很像,通过代理先对请求进行预处理,当达到某个可以接受的程度时,再转给本体对象处理。再举一个在线编辑器同步的例子。它不会你的内容一改变就立即给服务器发送同步请求,如果这样实现,当遇到一个手速惊人的用户时保证弄死服务器。所以我们一般用虚拟代理来收集变化,用节流思想发送请求。
let send = function(article) {
return $.ajax({
url: ...,
type: 'post',
contentType: 'text/plain',
data: article
})
};
let proxy = (function() {
let content = document.querySelector('#article'),
timer;
return function() {
let article = content.value;
if (timer) return;
timer = setTimeout(function() {
send(article).then(function() {
clearTimeout(timer);
timer = null;
})
}, 2000);
}
})();
如果不是对实时性要求非常高的系统,2秒延迟的副作用基本可以忽略,但是却大大减轻了服务器的压力。
缓存代理
缓存代理主要用来为一些重复性高开销又大的运算结果提供存储,在下次运算时如果参数一致则直接返回存储结果。在项目中比较常见的就是分页需求了。同一页的数据只需要在后台获取一次,下次再请求同一页时即可直接用缓存数据。例子如下:
let proxy = (function() {
let cache = {};
return (page, fn) {
let pageData = cache[page];
if (pageData) {
return fn(pageData);
}
http.getPage(page) // 后台获取数据
.then((data) => {
cache[page] = data; // 缓存新数据
fn(data);
}
} // fn处理页码
})();
继续优化的话可以用ES6的weak map来存储缓存数据保证集合结构的纯洁性。
小结
代理模式的主要特点就是代理对象并不影响本体对象的功能接口,主要目的在于信息的优化处理。所以我们在编写业务代码时,不必优先考虑实现代理模式。当发现本体对象数据处理不方便时,再编写代理模式即可。总的来说属于一个锦上添花的模式。
-- EOF --