javascript設計模式——代理模式

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/13/8030653.html
-Advertisement-
Play Games

[1]代理模式結構 [2]圖片預載入 [3]單一職責原則 [4]合併HTTP請求 [5]虛擬代理在惰性載入中的應用 [6]緩存代理 [7]動態創建代理 [8]其他代理模式 ...


前面的話

  代理模式是為一個對象提供一個占位符,以便控制對它的訪問。 代理模式是一種非常有意義的模式,在生活中可以找到很多代理模式的場景。比如,明星都有經紀人作為代理。如果想請明星來辦一場商業演出,只能聯繫他的經紀人。經紀人會把商業演出的細節和報酬都談好之後,再把合同交給明星簽。 代理模式的關鍵是當客戶不方便直接訪問一個對象或者不滿足需要的時候,提供一個替身對象來控制對這個對象的訪問,客戶實際上訪問的是替身對象。替身對象對請求做出一些處理之後,再把請求轉交給本體對象。本文將詳細介紹代理模式

 

代理模式結構

  比如,實現一個小明讓B代替自己向A送花的功能。首先,不引入代理,小明直接送花給A,代碼如下

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

  現在改變故事的背景設定,假設當A在心情好的時候收到花,小明表白成功的幾率有60%,而當A在心情差的時候收到花,小明表白的成功率無限趨近於0。小明跟A剛剛認識兩天,還無法辨別A什麼時候心情好。如果不合時宜地把花送給A,花被直接扔掉的可能性很大。但是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( B );

  雖然這隻是個虛擬的例子,但可以從中找到兩種代理模式的身影

【保護代理】

  代理B可以幫助A過濾掉一些請求,比如送花的人中年齡太大的或者沒有寶馬的,這種請求就可以直接在代理B處被拒絕掉。這種代理叫作保護代理

【虛擬代理】

  把newFlower的操作交給代理B去執行,代理B會選擇在A心情好時再執行newFlower,這是代理模式的另一種形式,叫作虛擬代理。虛擬代理把一些開銷很大的對象,延遲到真正需要它的時候才去創建。代碼如下:

var B = {
    receiveFlower: function( flower ){
        A.listenGoodMood(function(){ // 監聽A 的好心情
            A.receiveFlower( flower ); //延遲創建flower對象
        });
    }
};

 

圖片預載入

  在Web開發中,圖片預載入是一種常用的技術,如果直接給某個img標簽節點設置src屬性,由於圖片過大或者網路不佳,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張loading圖片占位,然後用非同步的方式載入圖片,等圖片載入好了再把它填充到img節點里,這種場景就很適合使用虛擬代理

  下麵來實現這個虛擬代理,首先創建一個普通的本體對象,這個對象負責往頁面中創建一個img標簽,並且提供一個對外的setSrc介面,外界調用這個介面,便可以給該img標簽設置

var myImage = (function(){
    var imgNode = document.createElement( 'img' );
    document.body.appendChild( imgNode );
    return {
        setSrc: function( src ){
            imgNode.src = src;
        }
    }
})();
myImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );

  現在開始引入代理對象proxyImage,通過這個代理對象,在圖片被真正載入好之前,頁面中將出現一張占位的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(){
        myImage.setSrc( this.src );
    }
    return {
        setSrc: function( src ){
            myImage.setSrc( 'loading.gif' );
            img.src = src;
        }
    }
})();
proxyImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );

  現在通過proxyImage間接地訪問myImage。proxyImage控制了客戶對myImage的訪問,並且在此過程中加入一些額外的操作,比如在真正的圖片載入好之前,先把img節點的src設置為一張本地的loading圖片

 

單一職責原則

  如果不使用代理,則圖片預載入的函數實現代碼如下

var MyImage = (function(){
    var imgNode = document.createElement( 'img' );
    document.body.appendChild( imgNode );
    var img = new Image;
    img.onload = function(){
        imgNode.src = img.src;
    };
    return {
        setSrc: function( src ){
            imgNode.src = 'loading.gif';
            img.src = src;
        }
    }
})();
MyImage.setSrc( 'https://static.xiaohuochai.site/icon/icon_200.png' );

  下麵引入一個面向對象設計的原則——單一職責原則

  單一職責原則指的是,就一個類(通常也包括對象和函數等)而言,應該僅有一個引起它變化的原因。如果一個對象承擔了多項職責,就意味著這個對象將變得巨大,引起它變化的原因可能會有多個。面向對象設計鼓勵將行為分佈到細粒度的對象之中,如果一個對象承擔的職責過多,等於把這些職責耦合到了一起,這種耦合會導致脆弱和低內聚的設計。當變化發生時,設計可能會遭到意外的破壞

  職責被定義為“引起變化的原因”。上面代碼中的MyImage對象除了負責給img節點設置src外,還要負責預載入圖片。在處理其中一個職責時,有可能因為其強耦合性影響另外一個職責的實現

  另外,在面向對象的程式設計中,大多數情況下,若違反其他任何原則,同時將違反開放——封閉原則。如果只是從網路上獲取一些體積很小的圖片,或者5年後的網速快到根本不再需要預載入,可能希望把預載入圖片的這段代碼從MyImage對象里刪掉。這時候就不得不改動 MyImage 對象了

  實際上,需要的只是給img節點設置src,預載入圖片只是一個錦上添花的功能。如果能把這個操作放在另一個對象裡面,自然是一個非常好的方法。於是代理的作用在這裡就體現出來了,代理負責預載入圖片,預載入的操作完成之後,把請求重新交給本體MyImage

  縱觀整個程式,並沒有改變或者增加MyImage的介面,但是通過代理對象,實際上給系統添加了新的行為。這是符合開放——封閉原則的。給img節點設置 src 和圖片預載入這兩個功能, 被隔離在兩個對象里,它們可以各自變化而不影響對方。何況就算有一天不再需要預載入, 那麼只需要改成請求本體而不是請求代理對象即可

【代理和本體介面的一致性】

  代理對象和本體都對外提供了setSrc方法,在客戶看來,代理對象和本體是一致的, 代理接手請求的過程對於用戶來說是透明的,用戶並不清楚代理和本體的區別,這樣做有兩個好處:1、用戶可以放心地請求代理,只關心是否能得到想要的結果;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( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
        img.src = src;

    }
})();
proxyImage( 'https://static.xiaohuochai.site/icon/icon_200.png' );

 

合併HTTP請求

  在Web開發中,也許最大的開銷就是網路請求。假設在做一個文件同步的功能,選中一個checkbox時,它對應的文件就會被同步到另外一臺備用伺服器上面

  首先,在頁面中放置好這些checkbox節點

<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
</body>

  接下來,給這些checkbox綁定點擊事件,並且在點擊的同時往另一臺伺服器同步文件

var synchronousFile = function( id ){
    console.log( '開始同步文件,id 為: ' + id );
};
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
    c.onclick = function(){
        if ( this.checked === true ){
            synchronousFile( this.id );
        }
    }
};

  當選中3個checkbox的時候,依次往伺服器發送了3次同步文件的請求。可以預見,如此頻繁的網路請求將會帶來相當大的開銷。解決方案是可以通過一個代理函數proxySynchronousFile來收集一段時間之內的請求,最後一次性發送給伺服器。比如等待2秒之後才把這2秒之內需要同步的文件ID打包發給伺服器,如果不是對實時性要求非常高的系統,2秒的延遲不會帶來太大副作用,卻能大大減輕伺服器的壓力

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

var proxySynchronousFile = (function(){
    var cache = [], // 保存一段時間內需要同步的ID
    timer; // 定時器
    return function( id ){
        cache.push( id );
        if ( timer ){ // 保證不會覆蓋已經啟動的定時器
            return;
        }
        timer = setTimeout(function(){
        synchronousFile( cache.join( ',' ) ); // 2 秒後向本體發送需要同步的ID 集合
        clearTimeout( timer ); // 清空定時器
        timer = null;
        cache.length = 0; // 清空ID 集合
    }, 2000 );
    }
})();

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

 

虛擬代理在惰性載入中的應用

  假設要載入miniConsole.js這個文件,該文件用於列印log,但該文件很大。所以,通常解決方案是用一個占位的miniConsole代理對象來給用戶提前使用,然後將列印log的請求都包裹在一個函數里,隨後這些函數將全部放到緩存隊列中,這些邏輯都是在miniConsole代理對象中完成實現的。等用戶按下F2喚出控制台的時候,才開始載入真正的miniConsole.js的代碼,載入完成之後將遍歷miniConsole代理對象中的緩存函數隊列,同時依次執行它們

  未載入真正的miniConsole.js之前的代碼如下:

var cache = [];
var miniConsole = {
    log: function(){
        var args = arguments;
        cache.push( function(){
            return miniConsole.log.apply( miniConsole, args );
        });
    }
};
miniConsole.log(1);

  當用戶按下F2時,開始載入真正的miniConsole.js,代碼如下:

var handler = function( ev ){
    if ( ev.keyCode === 113 ){
        var script = document.createElement( 'script' );
        script.onload = function(){
            for ( var i = 0, fn; fn = cache[ i++ ]; ){
                fn();
            }
        };
        script.src = 'miniConsole.js';
        document.getElementsByTagName( 'head' )[0].appendChild( script );
    }
};
document.body.addEventListener( 'keydown', handler, false );

// miniConsole.js 代碼:
miniConsole = {
    log: function(){
        // 真正代碼略
        console.log( Array.prototype.join.call( arguments ) );
    }
};

  要註意的是,要保證在F2被重覆按下時,miniConsole.js只被載入一次

var miniConsole = (function(){
    var cache = [];
    var handler = function( ev ){
        if ( ev.keyCode === 113 ){
            var script = document.createElement( 'script' );
            script.onload = function(){
                for ( var i = 0, fn; fn = cache[ i++ ]; ){
                    fn();
                }
            };
            script.src = 'miniConsole.js';
            document.getElementsByTagName( 'head' )[0].appendChild( script );
            document.body.removeEventListener( 'keydown', handler );// 只載入一次miniConsole.js
        }
    };
    document.body.addEventListener( 'keydown', handler, false );
    return {
        log: function(){
            var args = arguments;
            cache.push( function(){
                return miniConsole.log.apply( miniConsole, args );
            });
        }
    }
})();

miniConsole.log( 11 ); // 開始列印log
// miniConsole.js 代碼
miniConsole = {
    log: function(){
    // 真正代碼略
    console.log( Array.prototype.join.call( arguments ) );
    }
}

 

緩存代理

  緩存代理可以為一些開銷大的運算結果提供暫時的存儲,在下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果

  下麵是一個計算乘積的例子

  先創建一個用於求乘積的函數:

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

  然後加入緩存代理函數

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函數可以繼續專註於自身的職責——計算乘積,緩存的功能是由代理對象實現的

  在項目中常常遇到分頁的需求,同一頁的數據理論上只需要去後臺拉取一次,這些已經拉取到的數據在某個地方被緩存之後,下次再請求同一頁的時候,便可以直接使用之前的數據。顯然這裡也可以引入緩存代理,實現方式跟計算乘積的例子差不多,唯一不同的是,請求數據是個非同步的操作,無法直接把計算結果放到代理對象的緩存中,而是要通過回調的方式

 

動態創建代理

  通過傳入高階函數的方式,可以為各種計算方法創建緩存代理。現在這些計算方法被當作參數傳入一個專門用於創建緩存代理的工廠中,這樣一來,就可以為乘法、加法、減法等創建緩存代理,代碼如下:

/**************** 計算乘積 *****************/
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;
};
/**************** 創建緩存代理的工廠 *****************/
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

 

其他代理模式

  代理模式的變體種類非常多,還包括以下幾種

  1、防火牆代理:控制網路資源的訪問,保護主題不讓“壞人”接近

  2、遠程代理:為一個對象在不同的地址空間提供局部代表

  3、保護代理:用於對象應該有不同訪問許可權的情況

  4、智能引用代理:取代了簡單的指針,它在訪問對象時執行一些附加操作,比如計算一個對象被引用的次數

  5、寫時複製代理:通常用於複製一個龐大對象的情況。寫時複製代理延遲了複製的過程,當對象被真正修改時,才對它進行複製操作。寫時複製代理是虛擬代理的一種變體,DLL(操作系統中的動態鏈接庫)是其典型運用場景

  代理模式包括許多小分類,在javascript開發中最常用的是虛擬代理和緩存代理。雖然代理模式非常有用,但在編寫業務代碼時,往往不需要去預先猜測是否需要使用代理模式。當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.首先先要下載artTemplate.js,這個可以在官網下載也可以在GitHob進行下載。 2.現在是編寫一個存放html標簽的編寫模板,使用<script type="text/html" id="site_template"></script>裝載,裡面的id是你到時候將確認將數據傳給誰的綁 ...
  • WebpackOptionsDefaulter模塊 通過參數檢測後,會根據單/多配置進行處理,本文基於單配置,所以會進行到如下代碼: 模塊的作用是進行預設值的設置,流程圖如下: 進入該模塊: 可以看到,這個模塊的內容是用ES6的新語法寫的,很好理解,因為這個模塊是只是針對webpack的預設設置,所 ...
  • 最近學習vue.js,下麵是筆記: 重點在3. 可能出現的問題: 1,首先我這邊歷經了慘痛的教訓後,建議各位vue學友,一定要使用Administrator,系統管理員身份登錄,不要使用二級管理員,很容易在安裝腳手架或者其他工具時,出現許可權問題,,登錄Administrator操作電腦後很多亂七八糟 ...
  • 前言 通常,jQuery的函數ajax進行Ajax調用。函數ajax只能做一個Ajax調用。當Ajax調用成功時,執行回調函數。可選地,當Ajax調用返回錯誤時,調用另一個回調函數。但是,該功能不能根據這些請求的結果進行多個Ajax請求和註冊回調函數。一種情況是,網頁使多個Ajax請求在禁用用戶交互 ...
  • 今天看到一個關於foo的一個面試題,趕腳特別有意思 ...
  • 1. 寫在前面 往常都是利用 Python/.NET 語言實現爬蟲,然現在作為一名前端開發人員,自然需要熟練 NodeJS。下麵利用 NodeJS 語言實現一個糗事百科的爬蟲。另外,本文使用的部分代碼是 es6 語法。 實現該爬蟲所需要的依賴庫如下。 1. request: 利用 get 或者 po ...
  • 前兩天有一個原來的同事問我文字描邊怎麼做,那麼今天我們就來說說文字描邊這個樣式怎麼實現. 一.文字描邊 -webkit-text-stroke 文字描邊 參數:參數1 描邊大小 參數2 描邊顏色 註意: webkit內核有效 只能使用在谷歌 ,safir有效 二.實例 上圖的效果我們怎樣來實現呢? ...
  • validateSchema模塊 首先來看錯誤檢測: 可以註意到,這裡傳了兩個參數,其實第一個參數來源於一個JSON文件: 這個JSON文件非常大,可以觀察一下部分內容: 從描述可以猜測,這裡的key對應options中的key,value就是檢測方式。 比如說entry放到required代表是必 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...