javascript設計模式之訂閱者模式

一. 初識代理模式

代理模式是為一個對象提供一個代用品或占位符,以便控制對它的訪問。它的用處就是當客戶不方便直接訪問一個對象或者不滿足需要的時候,提供一個替身對象來控制對這個對象的訪問。通俗來講就是,代理是一個中間人,負責在客戶和賣傢之間傳遞信息,將沒用的信息過濾,將有利於交易成功的信息傳遞給賣傢,從而加大交易的成功率。

二. 代理模式的實現思想

現在我們來通過一個小明給女神送花的例子來實現代理模式。

沒有使用代理模式

        let Flower = function () {};
        let xiaomingFirst = {
            sendFlower ( target ) {
                let flower = new Flower();
                target.receiveFlower( flower );
            }
        }
        let A = {
            receiveFlower ( flower ) {
                console.log('收到花')
            }
        }
        xiaomingFirst.sendFlower(A);

使用代理模式重構

設定一個需求AA 為小明的女神,AA 在心情好的時候接受小明花的幾率更大,而 B 是 AA 和小明的好朋友,因此小明將花送給 B,讓 B 在 AA 心情好時轉告自己的心意。

分析:重構後的代碼增加瞭一個新的對象 B , 此時 為 AA 的代理,監聽 AA 的好心情,代碼中假定5秒後 AA 心情變好,將花送出。

        let xiaomingSencend = {
            sendFlower ( target ) {
                let flower = new Flower();
                target.receiveFlower( flower );
            }
        }
        let AA = {
            receiveFlower () {
                console.log('收到花');
            },
            listenGoodMood ( fn ) {
                setTimeout(()=>{
                    fn();
                }, 5000);
            }
        }
        let B = {
            receiveFlower ( flower ) {
                AA.listenGoodMood( ()=>{
                    AA.receiveFlower( flower );
                } );
            }
        }
        xiaomingSencend.sendFlower(B);

三. 代理模式分類

上述代碼中代理模式可能顯得不那麼重要,但是體現瞭代理的思想,假如女神有一些要求,給他送花的男生必須帥而有錢,但又不能顯得那麼勢力,因此代理可以幫其過濾掉這些不符合要求的男生,減少自己的許多麻煩,還能保持自己的美好形象這就是代理的用處。再者說,買一朵花很昂貴,而不是單單 new 這麼簡單,男生不想浪費自己的錢,因此想先確定女神是否接受自己的花,便讓代理幫忙詢問,如果女神接受則讓代理幫忙買一朵送給女神,這樣減少瞭男生的損失但也達成瞭目的,這便引出瞭以下兩種代理模式。

  • 保護代理:本體的要求直接在代理中實現,過濾掉不符合的要求的訪問者對本體的請求
  • 虛擬代理:將一些開銷很大的操作等到準備向本體請求時候再實現(主要用於實際開發)

四. 虛擬代理模式的實際運用

1. 虛擬代理實現圖片預加載

分析:先加載本地圖片,然後開始發起請求獲取圖片,當圖片加載獲取成功後,再調用 myImage 將圖片替換預加載時顯示的圖片。

    let myImage = function () {
        let img = document.createElement('img');
        document.body.appendChild(img);
        return {
            setSrc ( src ) {
                img.src = src;
            }
        }
    }();
    proxyImage = function () {
        let img = new Image;
        img.onload = function () {
            myImage.setSrc( this.src );
        }
        return {
            setProxyImage( src ) {
                myImage.setSrc("https://img.zcool.cn/community/[email protected]_1l_2o_100sh.jpg"); // 此處為加載 loading 圖片,用來占位,本地圖片(作者使用網絡圖片)
                img.src = src;
            }
        }
    }();
    proxyImage.setProxyImage("https://img.zcool.cn/community/[email protected]_1l_2o_100sh.jpg");

 2. 緩存代理

分析:在代理中將本體計算的結果進行緩存,如果下次再遇到同樣的請求,直接從緩存中獲取,減少對本體的訪問,減少性能消耗。

        // 計算乘積的函數
        let mult = function (...arg) {
            console.log('我執行瞭')
            let m = 1;
            for(let i = 0,l = arg.length; i <l; i++){
                m *= arg[i];
            }
            return m;
        }
        // 緩存代理
        let proxyMult = function( fn ) {
            let cache = {};
            return function (...arg) {
                let argS = arg.join(',');
                if( cache[argS] ) {
                    return cache[argS];
                }
                return cache[argS] = fn.apply( this, arg);
            }
        };
        let proxymult = proxyMult(mult);
        console.log(proxymult(1,2,4))

3. 虛擬代理合並 Http 請求

使用場景:點擊復選框向服務器發起請求同步文件,每次點擊復選框便發起一次請求,造成巨大網絡開銷,此時我們優化的方式為,將想同步的文件緩存下來,在 3 秒後通過一次請求發送到服務器。

    <input type="checkbox" >1
    <input type="checkbox" >2
    <input type="checkbox" >3
    <input type="checkbox" >4
    <input type="checkbox" >5
    <input type="checkbox" >6
    <input type="checkbox" >7
    <input type="checkbox" >8
    <script>
        // 同步文件
        let synchronousFile = function ( id ) {
            console.log("開始同步文件" + id);
        }
        // 代理實現同步文件
        let proxySynchronousFile = function ( ) {
            let cache = [];
            let time;
            return function ( id ) {
                cache.push( id );
                if( time ) {
                    return;
                }
                time = setInterval(() => {
                    synchronousFile(cache.join(','));
                    clearInterval(time);
                    time = null;
                    cache.length = 0;
                }, 3000);
            }
        }()
        // 添加點擊執行
        let checkboxList = document.getElementsByTagName('input');
        for(let i = 0, l = checkboxList.length; i < l; i++) {
            checkboxList[i].onclick = function() {
                if( this.checked === true ) { 
                    proxySynchronousFile(i+1);
                }
            }
        }

五. 代理的使用意義及要求

意義:首先我們引入面向對象設計原則,單一職責原則,一個對象應該盡可能少的承擔職責,最好是一個,如果承擔職責過多,會提高代碼的耦合度,從而導致脆弱和低內聚的設計。當變化發生,設計可能遭到意外破壞,即使實現同樣需求可以將代碼封裝到一個函數中,但是最好的處理措施是引入代理模式

要求:代理和本體接口要一致,在任何使用本體地方都可以用代理來代替

六. 總結

代理模式包括很多小模塊,最常用的為緩存代理 和 虛擬代理,雖然代理模式非常有用,但我們再編寫業務代碼時不需要預先猜測是否需要使用代理模式,到發現不方便直接訪問某個對象,真正使用時再編寫也不遲。

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: