代理是一個對象,它可以用來控制對本體對象的訪問,它與本體對象實現了同樣的接口,代理對象會把所有的調用方法傳遞給本體對象的;代理模式最基本的形式是對訪問進行控制,而本體對象則負責執行所分派的那個對象的函數或者類,簡單的來講本地對象注重的去執行頁面上的代碼,代理則控制本地對象何時被實例化,何時被使用;我們在上面的單體模式中使用過一些代理模式,就是使用代理模式實現單體模式的實例化,其他的事情就交給本體對象去處理;
代理的優點:
代理對象可以代替本體被實例化,并使其可以被遠程訪問;
它還可以把本體實例化推遲到真正需要的時候;對于實例化比較費時的本體對象,或者因為尺寸比較大以至于不用時不適于保存在內存中的本體,我們可以推遲實例化該對象;
我們先來理解代理對象代替本體對象被實例化的列子;比如現在京東ceo想送給奶茶妹一個禮物,但是呢假如該ceo不好意思送,或者由于工作忙沒有時間送,那么這個時候他就想委托他的經紀人去做這件事,于是我們可以使用代理模式來編寫如下代碼:
// 先申明一個奶茶妹對象
var TeaAndMilkGirl = function(name) {
this.name = name;
};
// 這是京東ceo先生
var Ceo = function(girl) {
this.girl = girl;
// 送結婚禮物 給奶茶妹
this.sendMarriageRing = function(ring) {
console.log("Hi " + this.girl.name + ", ceo送你一個禮物:" + ring);
}
};
// 京東ceo的經紀人是代理,來代替送
var ProxyObj = function(girl){
this.girl = girl;
// 經紀人代理送禮物給奶茶妹
this.sendGift = function(gift) {
// 代理模式負責本體對象實例化
(new Ceo(this.girl)).sendMarriageRing(gift);
}
};
// 初始化
var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));
proxy.sendGift("結婚戒"); // Hi 奶茶妹, ceo送你一個禮物:結婚戒
代碼如上的基本結構,TeaAndMilkGirl 是一個被送的對象(這里是奶茶妹);Ceo 是送禮物的對象,他保存了奶茶妹這個屬性,及有一個自己的特權方法sendMarriageRing 就是送禮物給奶茶妹這么一個方法;然后呢他是想通過他的經紀人去把這件事完成,于是需要創建一個經濟人的代理模式,名字叫ProxyObj ;他的主要做的事情是,把ceo交給他的禮物送給ceo的情人,因此該對象同樣需要保存ceo情人的對象作為自己的屬性,同時也需要一個特權方法sendGift ,該方法是送禮物,因此在該方法內可以實例化本體對象,這里的本體對象是ceo送花這件事情,因此需要實例化該本體對象后及調用本體對象的方法(sendMarriageRing).
最后我們初始化是需要代理對象ProxyObj;調用ProxyObj 對象的送花這個方法(sendGift)即可;
對于我們提到的優點,第二點的話,我們下面可以來理解下虛擬代理,虛擬代理用于控制對那種創建開銷很大的本體訪問,它會把本體的實例化推遲到有方法被調用的時候;比如說現在有一個對象的實例化很慢的話,不能在網頁加載的時候立即完成,我們可以為其創建一個虛擬代理,讓他把該對象的實例推遲到需要的時候。
理解使用虛擬代理實現圖片的預加載
在網頁開發中,圖片的預加載是一種比較常用的技術,如果直接給img標簽節點設置src屬性的話,如果圖片比較大的話,或者網速相對比較慢的話,那么在圖片未加載完之前,圖片會有一段時間是空白的場景,這樣對于用戶體驗來講并不好,那么這個時候我們可以在圖片未加載完之前我們可以使用一個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-121204193Q9-50.gif";
img.src = src;
}
}
})();
// 調用方式
myImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
如上代碼是不使用代理模式來實現的代碼;
第二種方案:使用代理模式來編寫預加載圖片的代碼如下:
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-121204193Q9-50.gif");
img.src = src;
}
}
})();
// 調用方式
ProxyImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
第一種方案是使用一般的編碼方式實現圖片的預加載技術,首先創建imgNode元素,然后調用myImage.setSrc該方法的時候,先給圖片一個預加載圖片,當圖片加載完的時候,再給img元素賦值,第二種方案是使用代理模式來實現的,myImage 函數只負責創建img元素,代理函數ProxyImage 負責給圖片設置loading圖片,當圖片真正加載完后的話,調用myImage中的myImage.setSrc方法設置圖片的路徑;他們之間的優缺點如下:
第一種方案一般的方法代碼的耦合性太高,一個函數內負責做了幾件事情,比如創建img元素,和實現給未加載圖片完成之前設置loading加載狀態等多項事情,未滿足面向對象設計原則中單一職責原則;并且當某個時候不需要代理的時候,需要從myImage 函數內把代碼刪掉,這樣代碼耦合性太高。
第二種方案使用代理模式,其中myImage 函數只負責做一件事,創建img元素加入到頁面中,其中的加載loading圖片交給代理函數ProxyImage 去做,當圖片加載成功后,代理函數ProxyImage 會通知及執行myImage 函數的方法,同時當以后不需要代理對象的話,我們直接可以調用本體對象的方法即可;
從上面代理模式我們可以看到,代理模式和本體對象中有相同的方法setSrc,這樣設置的話有如下2個優點:
用戶可以放心地請求代理,他們只關心是否能得到想要的結果。假如我門不需要代理對象的話,直接可以換成本體對象調用該方法即可。
在任何使用本體對象的地方都可以替換成使用代理。
當然如果代理對象和本體對象都返回一個匿名函數的話,那么也可以認為他們也具有一直的接口;比如如下代碼:
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return function(src){
imgNode.src = src;
}
})();
// 代理模式
var ProxyImage = (function(){
var img = new Image();
img.onload = function(){
myImage(this.src);
};
return function(src) {
myImage("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");
img.src = src;
}
})();
// 調用方式
ProxyImage("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
虛擬代理合并http請求的理解:
比如在做后端系統中,有表格數據,每一條數據前面有復選框按鈕,當點擊復選框按鈕時候,需要獲取該id后需要傳遞給給服務器發送ajax請求,服務器端需要記錄這條數據,去請求,如果我們每當點擊一下向服務器發送一個http請求的話,對于服務器來說壓力比較大,網絡請求比較頻繁,但是如果現在該系統的實時數據不是很高的話,我們可以通過一個代理函數收集一段時間內(比如說2-3秒)的所有id,一次性發ajax請求給服務器,相對來說網絡請求降低了, 服務器壓力減少了;
// 首先html結構如下:
<p>
<label>選擇框</label>
<input type="checkbox" class="j-input" data-id="1"/>
</p>
<p>
<label>選擇框</label>
<input type="checkbox" class="j-input" data-id = "2"/>
</p>
<p>
<label>選擇框</label>
<input type="checkbox" class="j-input" data-id="3"/>
</p>
<p>
<label>選擇框</label>
<input type="checkbox" class="j-input" data-id = "4"/>
</p>
一般的情況下 JS如下編寫
<script>
var checkboxs = document.getElementsByClassName("j-input");
for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) {
(function(i){
checkboxs[i].onclick = function(){
if(this.checked) {
var id = this.getAttribute("data-id");
// 如下是ajax請求
}
}
})(i);
}
</script>
下面我們通過虛擬代理的方式,延遲2秒,在2秒后獲取所有被選中的復選框的按鈕id,一次性給服務器發請求。
通過點擊頁面的復選框,選中的時候增加一個屬性isflag,沒有選中的時候刪除該屬性isflag,然后延遲個2秒,在2秒后重新判斷頁面上所有復選框中有isflag的屬性上的id,存入數組,然后代理函數調用本體函數的方法,把延遲2秒后的所有id一次性發給本體方法,本體方法可以獲取所有的id,可以向服務器端發送ajax請求,這樣的話,服務器的請求壓力相對來說減少了。
代碼如下:
// 本體函數
var mainFunc = function(ids) {
console.log(ids); // 即可打印被選中的所有的id
// 再把所有的id一次性發ajax請求給服務器端
};
// 代理函數 通過代理函數獲取所有的id 傳給本體函數去執行
var proxyFunc = (function(){
var cache = [], // 保存一段時間內的id
timer = null; // 定時器
return function(checkboxs) {
// 判斷如果定時器有的話,不進行覆蓋操作
if(timer) {
return;
}
timer = setTimeout(function(){
// 在2秒內獲取所有被選中的id,通過屬性isflag判斷是否被選中
for(var i = 0,ilen = checkboxs.length; i < ilen; i++) {
if(checkboxs[i].hasAttribute("isflag")) {
var id = checkboxs[i].getAttribute("data-id");
cache[cache.length] = id;
}
}
mainFunc(cache.join(',')); // 2秒后需要給本體函數傳遞所有的id
// 清空定時器
clearTimeout(timer);
timer = null;
cache = [];
},2000);
}
})();
var checkboxs = document.getElementsByClassName("j-input");
for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) {
(function(i){
checkboxs[i].onclick = function(){
if(this.checked) {
// 給當前增加一個屬性
this.setAttribute("isflag",1);
}else {
this.removeAttribute('isflag');
}
// 調用代理函數
proxyFunc(checkboxs);
}
})(i);
}
理解緩存代理:
緩存代理的含義就是對第一次運行時候進行緩存,當再一次運行相同的時候,直接從緩存里面取,這樣做的好處是避免重復一次運算功能,如果運算非常復雜的話,對性能很耗費,那么使用緩存對象可以提高性能;我們可以先來理解一個簡單的緩存列子,就是網上常見的加法和乘法的運算。代碼如下:
// 計算乘法
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