代理模式简述
代理是一个对象,跟本体对象具有相同的接口,以此达到对本体对象的访问控制。
简单言之,本体对象只注重业务逻辑的实现,代理则控制本体对象的实例化(何时实例化、何时被使用)。
代理模式的优点在于:代理对象可以代替本体对象被实例化,此时本体对象未真正实例化,等到合适时机再实例化。
代理模式可以延迟创建开销很大的本体对象,他会把本体的实例化推迟到有方法被调用时。
一个简单的例子
// 声明女孩对象
var girl = function (name) {
this.name = name;
};
// 声明男孩对象
var boy = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
alert("Hi " + girl.name + ", 男孩送你一个礼物:" + gift);
}
};
// 声明代理对象
var proxyObj = function (girl) {
this.girl = girl;
this.sendGift = function (gift) {
(new boy(girl)).sendGift(gift); // 替dudu送花咯
}
};
var proxy = new proxyObj(new girl("花花"));
proxy.sendGift("999朵玫瑰");
如上代码,girl是一个被送礼物的对象,boy是送礼物的对象,他保存了girl这个属性,还有一个送礼物的方法sendGift,然后他通过proxyObj去完成这件事,proxyObj就是代理,他把boy的礼物送给了girl,因此proxyObj同样需要保存girl的属性,同时也有sendGift方法,该方法实例化本体对象boy并调用了boy的sendGift方法,完成了boy送girl礼物的这个过程。
实战一:图片加载
在前端开发中,使用图片是非常常见的场景,如果直接给img标签设置src属性,如果图片过大,或网速比较慢,图片在加载过程中会有一段时间的空白,用户体验不好。
传统的解决方案
传统的解决方法是:在图片未加载完成之前,使用一个loading图标作为占位符,等图片完成加载后,再使用真实的图片地址替代loading图标。如下:
// 不使用代理的预加载图片函数如下
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function(){
imgNode.src = this.src;
};
return {
setSrc: function(src) {
imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif";
img.src = src;
}
}
})();
// 调用方式
myImage.setSrc("https://www.baidu.com/img/bd_logo1.png");
如上代码,这是使用一般的编码方式实现图片的预加载技术的方案,首先创建imgNode元素,然后调用myImage.setSrc该方法的时候,先给图片一个预加载图片,当图片加载完的时候,再给img元素赋值。
这种方案是可以实现功能,但也有比较明显的缺陷:耦合性太高,myImage函数违背了面向对象设计原则中的单一职责原则,同时完成了创建img,设置loading加载状态等多个任务。
此时就可以使用代理模式来实现~~~
代理模式解决方案
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 代理模式
var ProxyImage = (function(){
var img = new Image();
img.onload = function(){
myImage.setSrc(this.src);
};
return {
setSrc: function(src) {
myImage.setSrc("http://img.lanrentuku.com/img/allimg/1212/5-121204193R0.gif");
img.src = src;
}
}
})();
// 调用方式
ProxyImage.setSrc("https://www.baidu.com/img/bd_logo1.png");
如上代码,myImage 函数只负责做一件事,创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做,当图片加载成功后,代理函数ProxyImage 会通知及执行myImage 函数的方法,同时当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可。
代理模式跟本体对象具有相同的对外接口,有两个好处:
一、用户可以放心地请求代理,不需要了解代理的实现过程,只要结果符合预期即可。如果不需要代理对象了,可以换成调用本体对象的该方法
二、在任何使用本体对象的地方,都可以使用代理替换。
最后,强调一点,主体对象跟代理对象也可以都返回一个匿名函数,这样也认为他们具有相同的接口。
实战二:缓存代理
对第一次运行的结果进行缓存,当再一次运行相同运算的时候,直接从缓存里面取,避免重复运算,如果运算非常复杂的话,对性能很耗费,那么使用缓存对象可以提高性能。以下是一个简单的例子:
var mult = function(){
var a = 1;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a = a*arguments[i];
}
return a;
};
// 计算加法
var plus = function(){
var a = 0;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a += arguments[i];
}
return a;
}
// 代理函数
var proxyFunc = function(fn) {
var cache = {}; // 缓存对象
return function(){
var args = Array.prototype.join.call(arguments,',');
if(args in cache) {
return cache[args]; // 使用缓存代理
}
return cache[args] = fn.apply(this,arguments);
}
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 缓存取 24
var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4)); // 10
console.log(proxyPlus(1,2,3,4)); // 缓存取 10
如上代码就是网上常见的加法和乘法的运算。通过缓存代理,可以减少不必要的运算量。
网友评论