什麼是代理模式?
首先我們先看一個有趣的例子
在四月一個晴朗的早晨,小明遇見了他的百分百女孩,我們暫且稱呼小明的女神爲A。兩天之後,小明決定給A送一束花來表白。剛好小明打聽到A和他有一個共同的朋友B,於是內向的小明決定讓B來代替自己完成送花這件事情。雖然小明的故事必然以悲劇收場,因爲追MM更好的方式是送一輛寶馬。不管怎樣,我們還是先用代碼來描述一下小明追女神的過程,先看看不用代理模式的情況:
// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
// 送花的方法,參數爲送花目標
sendFlower: function( target ){
// 創建一個花的實例
var flower = new Flower();
// 送給目標
target.receiveFlower( flower );
}
};
// 女神
var A = {
// 女神接收到了花
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
}
};
// 小明開始送花
xiaoming.sendFlower( A );
接下來,我們引入代理B,即小明通過B來給A送花:
// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
// 送花的方法,參數爲送花目標
sendFlower: function( target ){
// 創建一個花的實例
var flower = new Flower();
// 送給目標
target.receiveFlower( flower );
}
};
// 女神閨蜜
var B = {
// 小明把花送給了女神閨蜜,由他轉送
receiveFlower: function( flower ){
// 女神閨蜜把花送給了女神
A.receiveFlower(flower)
}
};
// 女神
var A = {
// 女神接收到了花
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
}
};
// 小明開始送花
xiaoming.sendFlower( A );
很顯然,執行結果跟第一段代碼一致,至此我們就完成了一個最簡單的代理模式的編寫。
也許讀者會疑惑,小明自己去送花和代理B幫小明送花,二者看起來並沒有本質的區別,引入一個代理對象看起來只是把事情搞複雜了而已。
但是,假設當A在心情好的時候收到花,小明表白成功的機率有60%,而當A在心情差的時候收到花,小明表白的成功率無限趨近於0。
小明跟A剛剛認識兩天,還無法辨別A什麼時候心情好。如果不合時宜地把花送給A,花被直接扔掉的可能性很大,這束花可是小明吃了7天泡麪換來的。
但是A的朋友B卻很瞭解A,所以小明只管把花交給B,B會監聽A的心情變化,然後選擇A心情好的時候把花轉交給A,代碼如下:
// 首先的有花的實例
var Flower = function(){};
var xiaoming = {
// 送花的方法,參數爲送花目標
sendFlower: function( target ){
// 創建一個花的實例
var flower = new Flower();
// 送給目標
target.receiveFlower( flower );
}
};
// 女神閨蜜
var B = {
// 小明把花送給了女神閨蜜,由他轉送
receiveFlower: function( flower ){
// 女神閨蜜看到女神心情好把花送給了女神
A.listenGoodMood(function(){ // 監聽A的好心情
A.receiveFlower( flower );
});
}
};
// 女神
var A = {
// 女神接收到了花
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
},
// 女神心情好了
listenGoodMood: function( fn ){
setTimeout(function(){ // 假設10秒之後A的心情變好
fn();
}, 10000 );
}
};
// 小明開始送花
xiaoming.sendFlower( A );
虛擬代理
虛擬代理把一些開銷很大的對象,延遲到真正需要它的時候纔去創建。
虛擬代理實現圖片預加載
在Web開發中,圖片預加載是一種常用的技術,如果直接給某個img標籤節點設置src屬性, 由於圖片過大或者網絡不佳,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張loading圖片佔位,然後用異步的方式加載圖片,等圖片加載好了再把它填充到img節點裏,這種場景就很適合使用虛擬代理。
下面我們來實現這個虛擬代理,首先創建一個普通的本體對象,這個對象負責往頁面中創建一個img標籤,並且提供一個對外的setSrc接口,外界調用這個接口,便可以給該img標籤設置src屬性:
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
i mgNode.src = src;
}
}
})();
myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg')
引入虛擬代理模式
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( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
}
})();
proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
現在我們通過proxyImage間接地訪問MyImage。proxyImage控制了客戶對MyImage的訪問,並且在此過程中加入一些額外的操作,比如在真正的圖片加載好之前,先把img節點的src設置爲一張本地的loading圖片。
緩存代理
緩存代理可以爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果。
緩存代理的例子——計算乘積
var mult = function(){
console.log( '開始計算乘積' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
// 加入緩存代理
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = mult.apply( this, arguments );
}
})();
proxyMult( 1, 2, 3, 4 ); // 輸出:24
proxyMult( 1, 2, 3, 4 ); // 輸出:24
當我們第二次調用proxyMult(1,2,3,4)的時候,本體mult函數並沒有被計算,proxyMult直接返回了之前緩存好的計算結果。
通過增加緩存代理的方式,mult函數可以繼續專注於自身的職責——計算乘積,緩存的功能是由代理對象實現的。
其他代理模式
- 防火牆代理:控制網絡資源的訪問,保護主題不讓“壞人”接近。
- 遠程代理:爲一個對象在不同的地址空間提供局部代表
- 保護代理:用於對象應該有不同訪問權限的情況。
- 智能引用代理:取代了簡單的指針,它在訪問對象時執行一些附加操作,比如計算一個對象被引用的次數。
- 寫時複製代理:通常用於複製一個龐大對象的情況。寫時複製代理延遲了複製的過程,當對象被真正修改時,纔對它進行復制操作。寫時複製代理是虛擬代理的一種變體,DLL(操作系統中的動態鏈接庫)是其典型運用場景。