javascript設計模式之三——代理模式

代理模式

代理模式:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。
代理模式是比較有用途的一種模式,而且變種較多( 虛擬代理、遠程代理、copy-on-write代理、保護代理、Cache代理、防火牆代理、同步代理、智能指引 ),應用場合覆蓋從小結構到整個系統的大結構,我們也許有代理服務器等概念,代理概念可以解釋爲:在出發點到目的地之間有一道中間層,意爲代理。
應用場景:
遠程代理:也就是爲了一個對象在不同的地址空間提供局部代表,這樣可以隱藏一個對象存在於不同地址空間的事實,就像web service裏的代理類一樣。
虛擬代理:根據需要創建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象,比如瀏覽器的渲染的時候先顯示問題,而圖片可以慢慢顯示(就是通過虛擬代理代替了真實的圖片,此時虛擬代理保存了真實圖片的路徑和尺寸。
代碼實現:

// 圖片加載函數
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("images/image1.jpg");
            img.src = src;
        }
    }
})();

// 調用代理對象加載圖片
proxyImage.setSrc( "http://images/qq.jpg");

下面是代理模式實現多圖片加載的例子:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<button id='btnLoadImg'>加載圖片</button>
<br>
<div id='imgContainer'>
</div>
<br>

<script type='text/javascript' src="js/jquery.js"></script>
<script type='text/javascript'>
//圖片地址保存在imgSrcs中
    var imgSrcs = [
        'http://www.carsceneuk.com/wp-content/uploads/2015/03/88y9989.jpg',
        'http://mfiles.sohu.com/20130223/5ff_403b2e7a_7a1f_7f24_66eb_79e3f27d58cf_1.jpg',
        'http://img1.imgtn.bdimg.com/it/u=2678963350,1378052193&fm=21&gp=0.jpg'
    ];

//頁面加載完後開始運行 按鈕綁定點擊事件,一張一張加載圖片
    $(document).ready(function(){
        $('#btnLoadImg').bind('click', function(){
            doLoadImgs(imgSrcs);
        });
    });

    //創建img標籤
    //這裏用自執行函數加一個閉包,是爲了可以創建多個id不同的img標籤。
    var createImgElement = (function(){
        var index = 0;

        return function() {
            var eleImg = document.createElement('img');
            eleImg.setAttribute('width', '200');
            eleImg.setAttribute('heght', '150');
            eleImg.setAttribute('id', 'img' + index++);
            return eleImg;
        };
    })();

    function loadImg(img, src) {
        img.src = src;
    }

    function createLoadImgProxy(){
        var imgCache = new Image();
        var dfd = $.Deferred();
        var timeoutTimer;

        //開始加載超時監控,超時後進行reject操作
        function beginTimeoutWatcher(){
            timeoutTimer = setTimeout(function(){
                dfd.reject('timeout');
            }, 10000);
        }

        //結束加載超時監控
        function endTimeoutWatcher(){
            if(!timeoutTimer){
                return;
            }

            clearTimeout(timeoutTimer);
        }

        //加載完成事件處理,加載完成後進行resolve操作
        imgCache.onload = function(){
            dfd.resolve(this.src);
        };

        //加載終止事件處理,終止後進行reject操作
        imgCache.onabort = function(){
            dfd.reject("aborted");
        };

        //加載異常事件處理,異常後進行reject操作
        imgCache.onerror = function(){
            dfd.reject("error");
        };

        return function(eleImg, src){

            dfd.always(function(){
//                        alert('always end');
                //加載完成或加載失敗都要終止加載超時監控
                endTimeoutWatcher();
            }).done(function(src){
//                        alert('done end');
                //加載完成後,往圖片元素上設置圖片
                loadImg(eleImg, src);
            }).fail(function(msg){
//                        alert('fail end:' + msg);
                //加載失敗後,往圖片元素上設置失敗圖片
                loadImg(eleImg, 'images/image1.jpg');
            });

            loadImg(eleImg, 'loading.gif');
            imgCache.src = src;

            //開始進行超時加載監控
            beginTimeoutWatcher();

            return dfd.promise();
        };
    }

    //一張一張的連續加載圖片
    //參數:
    //  srcs: 圖片路徑數組
    function doLoadImgs(srcs){
        var index = 0;

        (function loadOneByOne(){
            //退出條件
            if(!(s = srcs[index++])) {
                return;
            }

            var eleImg = createImgElement();
            document.getElementById('imgContainer').appendChild(eleImg);

            //創建一個加載代理函數
            var loadImgProxy = createLoadImgProxy();

            //在當前圖片加載或失敗後,遞歸調用,加載下一張
            loadImgProxy(eleImg, s).always(loadOneByOne);
        })();
    }
</script>
</body>
</html>

應用2:虛擬代理合並http請求

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <input type="checkbox" id="1"></input>1
    <input type="checkbox" id="2"></input>2
    <input type="checkbox" id="3"></input>3
    <input type="checkbox" id="4"></input>4
    <input type="checkbox" id="5"></input>5
    <input type="checkbox" id="6"></input>6
    <input type="checkbox" id="7"></input>7
    <input type="checkbox" id="8"></input>8
    <input type="checkbox" id="9"></input>9

<script type='text/javascript' src="js/jquery.js"></script>
<script type='text/javascript'>

    var synchronousFile = function( id ){
        console.log( '開始同步文件,id爲: ' + id );
    };


    var proxySynchronousFile = (function(){
        var cache = [],
        timer;
        return function(id){
            cache.push(id);
            if(timer){
                return;
            }
            timer = setTimeout(function(){
                synchronousFile(cache.join(','));
                clearTimeout(timer);
                timer = null;
                cache.length = 0; // 清空ID集合
            },20000);


        }
    })();

    var checkbox = document.getElementsByTagName( 'input' );
    for ( var i = 0, c; c = checkbox[ i++ ]; ){
        c.onclick = function(){
            if ( this.checked === true ){
                proxySynchronousFile( this.id );
            }
        }
    };

</script>
</body>
</html>

將Http請求收集一段時間,最後一次性發送給服務器,減少了頻繁向服務器提出請求的次數,大大減輕了服務器的壓力,改善了性能。
緩存代理:緩存代理可以爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果。
代碼實現:
在不使用緩存代理時,代碼可能會這樣寫:

var mult = function(){
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

這樣寫雖然實現了基本的功能,但是存在的問題是:如果多次計算mult( 2, 3 )時,會多次調用該函數,增大了運算的開銷,我們應該將第一次運算的結果保存,當第二次運算同樣的數據時,應該直接返回存儲的結果。
將上述代碼用代理模式設計後,代碼如下:

 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 );
        }
    })();

    alert( proxyMult( 1, 2, 3, 4 ));
alert( proxyMult( 1, 2, 3, 4 ));

用代理模式設計代碼後,運行時,可以發現console.log( ‘開始計算乘積’ );只打印了一次,proxyMult( 1, 2, 3, 4 )的結果出現兩次。第二次是從緩存中獲得。
安全代理:用來控制真實對象訪問時的權限,一般用於對象應該有不同的訪問權限。
智能指引:只當調用真實的對象時,代理處理另外一些事情。例如C#裏的垃圾回收,使用對象的時候會有引用次數,如果對象沒有引用了,GC就可以回收它了。
防火牆代理:控制網絡資源的訪問,保護主題不讓“壞人”接近。
代理模式種類很多,這裏只介紹了javascript中運用較多的虛擬代理和緩存代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章