JS設計模式之代理模式

代理是一個對象,它可以用來控制對本體對象的訪問,它與本體對象實現了同樣的接口,代理對象會把所有的調用方法傳遞給本體對象的;代理模式最基本的形式是對訪問進行控制,而本體對象則負責執行所分派的那個對象的函數或者類,簡單的來講本地對象注重的去執行頁面上的代碼,代理則控制本地對象何時被實例化,何時被使用;我們在上面的單體模式中使用過一些代理模式,就是使用代理模式實現單體模式的實例化,其他的事情就交給本體對象去處理;

代理的優點:

代理對象可以代替本體被實例化,并使其可以被遠程訪問;
它還可以把本體實例化推遲到真正需要的時候;對于實例化比較費時的本體對象,或者因為尺寸比較大以至于不用時不適于保存在內存中的本體,我們可以推遲實例化該對象;
我們先來理解代理對象代替本體對象被實例化的列子;比如現在京東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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 工廠模式類似于現實生活中的工廠可以產生大量相似的商品,去做同樣的事情,實現同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,798評論 2 17
  • 代理模式 代理是一個對象,它可以用來控制對本體對象的訪問,它與本體對象實現了同樣的接口,代理對象會把所有的調用方法...
    塵中老閱讀 368評論 0 0
  • 假如現在有一個需求是向服務器同步十個checkbox的狀態,如果是傳統寫法的話就是判斷checkbox的狀態判斷選...
    __Seve閱讀 307評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,665評論 25 708
  • 一把相同的種子,撒到地里,有的得到合適的水分和充足的日照,有的既干旱又曬不著太陽,最后差異當然會很大。閱讀就是智慧...
    小空同學閱讀 246評論 0 0