代理模式是為一個對象提供一個代用品或占位符括荡,以便控制對它的訪問。
????代理模式是一種非常有意義的模式提揍,在生活中可以找到很多代理模式的場景低匙。比如,明星都有經(jīng)紀人作為代理碳锈。如果想請明星來辦一場商業(yè)演出顽冶,只能聯(lián)系他的經(jīng)紀人。經(jīng)紀人會把商業(yè)演出的細節(jié)和報酬都談好之后售碳,再把合同交給明星簽强重。
????代理模式的關(guān)鍵是,當(dāng)客戶不方便直接訪問一個對象或者不滿足需要的時候贸人,提供一個替身對象來控制對這個對象的訪問间景,客戶實際上訪問的是替身對象。替身對象對請求做出一些處理之后艺智,再把請求轉(zhuǎn)交給本體對象倘要。如圖 所示。
保護代理和虛擬代理
????保護代理用于控制不同權(quán)限的對象對目標對象的訪問十拣,但在 JavaScript 并不容易實現(xiàn)保護代理封拧,因為我們無法判斷誰訪問了某個對象。而虛擬代理是最常用的一種代理模式夭问,我們主要討論的也是虛擬代理泽西。
虛擬代理實現(xiàn)圖片預(yù)加載
????在 Web 開發(fā)中,圖片預(yù)加載是一種常用的技術(shù)缰趋,如果直接給某個 img 標簽節(jié)點設(shè)置 src 屬性捧杉,由于圖片過大或者網(wǎng)絡(luò)不佳陕见,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張loading 圖片占位味抖,然后用異步的方式加載圖片评甜,等圖片加載好了再把它填充到 img 節(jié)點里,這種場景就很適合使用虛擬代理仔涩。
????下面我們來實現(xiàn)這個虛擬代理忍坷,首先創(chuàng)建一個普通的本體對象,這個對象負責(zé)往頁面中創(chuàng)建一個 img 標簽红柱,并且提供一個對外的 setSrc 接口承匣,外界調(diào)用這個接口,便可以給該 img 標簽設(shè)置src 屬性:
var myImage = (function(){
????var imgNode = document.createElement( 'img' );
????document.body.appendChild( imgNode );
????return {
????????setSrc: function( src ){
????????????imgNode.src = src;
????????}
????}
})();
myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
????我們把網(wǎng)速調(diào)至 5KB/s锤悄,然后通過 MyImage.setSrc 給該 img 節(jié)點設(shè)置 src韧骗,可以看到,在圖片被加載好之前零聚,頁面中有一段長長的空白時間袍暴。
????現(xiàn)在開始引入代理對象 proxyImage,通過這個代理對象隶症,在圖片被真正加載好之前政模,頁面中將出現(xiàn)一張占位的菊花圖 loading.gif, 來提示用戶圖片正在加載。代碼如下:
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(){? ? // 圖片加載完成后觸發(fā)
????????myImage.setSrc( this.src ); // this => img? ?即 img.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' );
????現(xiàn)在我們通過 proxyImage 間接地訪問 MyImage蚂会。proxyImage 控制了客戶對 MyImage 的訪問淋样,并且在此過程中加入一些額外的操作,比如在真正的圖片加載好之前胁住,先把 img 節(jié)點的 src 設(shè)置為一張本地的 loading 圖片趁猴。
? ??縱觀整個程序,我們并沒有改變或者增加 MyImage 的接口彪见,但是通過代理對象儡司,實際上給系統(tǒng)添加了新的行為。這是符合開放—封閉原則的余指。給 img 節(jié)點設(shè)置 src 和圖片預(yù)加載這兩個功能捕犬,被隔離在兩個對象里,它們可以各自變化而不影響對方酵镜。何況就算有一天我們不再需要預(yù)加載碉碉,那么只需要改成請求本體而不是請求代理對象即可。
緩存代理
? ? 緩存代理可以為一些開銷大的運算結(jié)果提供暫時的存儲笋婿,在下次運算時誉裆,如果傳遞進來的參數(shù)跟之前一致,則可以直接返回前面存儲的運算結(jié)果缸濒。
緩存代理的例子——計算乘積
先創(chuàng)建一個用于求乘積的函數(shù):
var mult = function(){
????console.log( '開始計算乘積' );
????var a = 1;
????for ( var i = 0, l = arguments.length; i < l; i++ ){
????????a = a * arguments[i];
????}
????return a;
};
mult( 2, 3 ); // 輸出:6
mult( 2, 3, 4 ); // 輸出:24
// 現(xiàn)在加入緩存代理函數(shù):
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
????當(dāng)我們第二次調(diào)用 proxyMult( 1, 2, 3, 4 )的時候足丢,本體 mult 函數(shù)并沒有被計算,proxyMult直接返回了之前緩存好的計算結(jié)果庇配。
????通過增加緩存代理的方式斩跌,mult 函數(shù)可以繼續(xù)專注于自身的職責(zé)——計算乘積,緩存的功能是由代理對象實現(xiàn)的捞慌。
用高階函數(shù)動態(tài)創(chuàng)建代理
????通過傳入高階函數(shù)這種更加靈活的方式耀鸦,可以為各種計算方法創(chuàng)建緩存代理。現(xiàn)在這些計算方法被當(dāng)作參數(shù)傳入一個專門用于創(chuàng)建緩存代理的工廠中啸澡, 這樣一來袖订,我們就可以為乘法、加
法嗅虏、減法等創(chuàng)建緩存代理洛姑,代碼如下:
/**************** 計算乘積 *****************/
var mult = function(){
????var a = 1;
????for ( var i = 0, l = arguments.length; i < l; i++ ){
????????a = a * arguments[i];
????}
????return a;
};
/**************** 計算加和 *****************/
var plus = function(){
????var a = 0;
????for ( var i = 0, l = arguments.length; i < l; i++ ){
????????a = a + arguments[i];
????}
????return a;
};
/**************** 創(chuàng)建緩存代理的工廠 *****************/
var createProxyFactory = 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 = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 輸出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 輸出:10
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ——摘自《JavaScript設(shè)計模式與開發(fā)實踐 》