js之發佈 — 訂閱模式

有人發佈信息有人“訂閱”信息並進行處理。

DOM事件

document.body.addEventListener( 'click', function(){
    alert(2);
}, false );
document.body.click(); // 模擬用戶點擊

售樓處level01

售樓處發佈樓盤信息,訂閱者訂閱(即把你需要的行爲存入售樓處)這些信息。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var salesOffices = {}; // 定義售樓處
        salesOffices.clientList = []; // 緩存列表,存放訂閱者的回調函數(需要的行爲)。
        salesOffices.listen = function( fn ){ // 增加訂閱者(添加需要的行爲)
            this.clientList.push( fn ); // 訂閱的消息添加進緩存列表
        };
        salesOffices.trigger = function(){ // 發佈消息(依次執行添加了的行爲)
            for( var i = 0, fn; fn = this.clientList[ i++ ]; ){
                fn.apply( this, arguments ); // (2) // arguments 是發佈消息時帶上的參數
            }
        };
//        下面我們來進行一些簡單的測試:
        salesOffices.listen( function( price, squareMeter ){ // 小明訂閱消息
            console.log( '價格= ' + price );
            console.log( 'squareMeter= ' + squareMeter );
        });

        salesOffices.trigger( 2000000, 88 ); // 輸出:200 萬,88 平方米
        salesOffices.trigger( 3000000, 110 ); // 輸出:300 萬,110 平方米
    </script>
</body>
</html>

缺點:沒有訂閱的也會發來。


售樓處level02

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var salesOffices = {}; // 定義售樓處
        salesOffices.clientList = {}; // 緩存列表,存放訂閱者的回調函數
        salesOffices.listen = function( key, fn ){
            if ( !this.clientList[ key ] ){ // 如果還沒有訂閱過此類消息,給該類消息創建一個緩存列表
                this.clientList[ key ] = [];
            }
            this.clientList[ key ].push( fn ); // 訂閱的消息添加進消息緩存列表
        };
        salesOffices.trigger = function(){ // 發佈消息
            var key = Array.prototype.shift.call( arguments ), // 取出消息類型
                fns = this.clientList[ key ]; // 取出該消息對應的回調函數集合
            if ( !fns || fns.length === 0 ){ // 如果沒有訂閱該消息,則返回
                return false;
            }
            for( var i = 0, fn; fn = fns[ i++ ]; ){
                fn.apply( this, arguments ); // (2) // arguments 是發佈消息時附送的參數
            }
        };
        salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱 88 平方米房子的消息
            console.log( '價格= ' + price ); // 輸出: 2000000
        });

        salesOffices.listen( 'squareMeter110', function( price ){ // 小紅訂閱 110 平方米房子的消息
            console.log( '價格= ' + price ); // 輸出: 3000000
        });
        salesOffices.trigger( 'squareMeter88', 2000000 ); // 發佈 88 平方米房子的價格
        salesOffices.trigger( 'squareMeter110', 3000000 ); // 發佈 110 平方米房子的價格
    </script>
</body>
</html>

解決了level01的問題
接受者只要有相同key的的接受者都可以接受到信息。


網站登錄level01

login.succ(function(data){
            header.setAvatar( data.avatar); // 設置 header 模塊的頭像
            nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
            message.refresh(); // 刷新消息列表
            cart.refresh(); // 刷新購物車列表
        });

等到有一天,項目中又新增了一個收貨地址管理的模塊,這個模塊本來是另一個同事所寫的,而此時你正在馬來西亞度假,但是他卻不得不給你打電話:“Hi,登錄之後麻煩刷新一下收貨地址列表。”於是你又翻開你 3個月前寫的登錄模塊,在最後部分加上這行代碼:

login.succ(function(data){
            header.setAvatar( data.avatar); // 設置 header 模塊的頭像
            nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
            message.refresh(); // 刷新消息列表
            cart.refresh(); // 刷新購物車列表
            address.refresh(); // 增加這行代碼
        });

網站登錄level02

用發佈 — 訂閱模式重寫之後,對用戶信息感興趣的業務模塊將自行訂閱登錄成功的消息事件。當登錄成功時,登錄模塊只需要發佈登錄成功的消息,而業務方接受到消息之後,就會開始進行
各自的業務處理,登錄模塊並不關心業務方究竟要做什麼,也不想去了解它們的內部細節。改善後的代碼如下:

<script>
       /* login.succ(function(data){
            header.setAvatar( data.avatar); // 設置 header 模塊的頭像
            nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
            message.refresh(); // 刷新消息列表
            cart.refresh(); // 刷新購物車列表
        });*/
        $.ajax( 'http:// xxx.com?login', function(data){ // 登錄成功
            login.trigger( 'loginSucc', data); // 發佈登錄成功的消息
        });
        var header = (function(){ // header 模塊
            login.listen( 'loginSucc', function( data){
                header.setAvatar( data.avatar );
            });
            return {
                setAvatar: function( data ){
                    console.log( '設置 header 模塊的頭像' );
                }
            }
        })();
        var nav = (function(){ // nav 模塊
            login.listen( 'loginSucc', function( data ){
                nav.setAvatar( data.avatar );
            });
            return {
                setAvatar: function( avatar ){
                    console.log( '設置 nav 模塊的頭像' );
                }
            }
        })();

     /*   如果有一天在登錄完成之
        後,又增加一個刷新收貨地址列表的行爲,那麼只要在收貨地址模塊里加上監聽消息的方法即可,
        而這可以讓開發該模塊的同事自己完成,你作爲登錄模塊的開發者,永遠不用再關心這些行爲了。
        代碼如下:*/
        var address = (function(){ // nav 模塊
            login.listen( 'loginSucc', function( obj ){
                address.refresh( obj );
            });
            return {
                refresh: function( avatar ){
                    console.log( '刷新收貨地址列表' );
                }
            }
        })();

售樓處 level03:全局的發佈 - 訂閱對象

發佈 — 訂閱模式可以用一個全局的 Event 對象來實現,訂閱者不需要了解消息來自哪個發佈者,發佈者也不知道消息會推送給哪些訂閱者, Event 作爲一個類似“中介者”的角色,把訂閱者和發佈者聯繫起來。見如下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var Event = (function(){
            var clientList = {},
                listen,
                trigger,
                remove;
            listen = function( key, fn ){
                if ( !clientList[ key ] ){
                    clientList[ key ] = [];
                }
                clientList[ key ].push( fn );
            };
            trigger = function(){
                var key = Array.prototype.shift.call( arguments ),
                    fns = clientList[ key ];
                if ( !fns || fns.length === 0 ){
                    return false;
                }
                for( var i = 0, fn; fn = fns[ i++ ]; ){
                    fn.apply( this, arguments );
                }
            };
            remove = function( key, fn ){
                var fns = clientList[ key ];
                if ( !fns ){
                    return false;
                }
                if ( !fn ){
                    fns && ( fns.length = 0 );
                }else{
                    for ( var l = fns.length - 1; l >=0; l-- ){
                        var _fn = fns[ l ];
                        if ( _fn === fn ){
                            fns.splice( l, 1 );
                        }
                    }
                }
            };
            return {
                listen: listen,
                trigger: trigger,
                remove: remove
            }
        })();
        Event.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price ); // 輸出:'價格=2000000'
        });

        Event.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price ); // 輸出:'價格=2000000'
        });
        Event.listen( 'squareMeter88', fn=function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price ); // 輸出:'價格=2000000'
        });
        Event.remove( 'squareMeter88');

        Event.trigger( 'squareMeter88', 2000000 ); // 售樓處發佈消息
    </script>
</body>
</html>

網站登錄level04

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="PublishSubscribe.js"></script>
</head>
<body>
    <script>
        var event = {
            clientList: [],
            listen: function( key, fn ){
                if ( !this.clientList[ key ] ){
                    this.clientList[ key ] = [];
                }
                this.clientList[ key ].push( fn ); // 訂閱的消息添加進緩存列表
            },
            trigger: function(){
                var key = Array.prototype.shift.call( arguments ), // (1);
                    fns = this.clientList[ key ];
                if ( !fns || fns.length === 0 ){ // 如果沒有綁定對應的消息
                    return false;
                }
                for( var i = 0, fn; fn = fns[ i++ ]; ){
                    fn.apply( this, arguments ); // (2) // arguments 是 trigger 時帶上的參數
                }
            },
            remove : function( key, fn ){
                var fns = clientList[ key ];
                if ( !fns ){
                    return false;
                }
                if ( !fn ){
                    fns && ( fns.length = 0 );
                }else{
                    for ( var l = fns.length - 1; l >=0; l-- ){
                        var _fn = fns[ l ];
                        if ( _fn === fn ){
                            fns.splice( l, 1 );
                        }
                    }
                }
            }
        };
        var installEvent = function( obj ){
            for ( var i in event ){
                obj[ i ] = event[ i ];
            }
        };
        var salesOffices = {};
        installEvent( salesOffices );
        salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱消息
            console.log( '價格= ' + price );
        });
        salesOffices.listen( 'squareMeter100', function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price );
        });
        /*salesOffices.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price ); // 輸出:'價格=2000000'
        });
         salesOffices.listen( 'squareMeter88', fn=function( price ){ // 小紅訂閱消息
            console.log( '價格= ' + price ); // 輸出:'價格=2000000'
        });
        Event.remove( 'squareMeter88');*/
        salesOffices.trigger( 'squareMeter88', 2000000 ); // 輸出:2000000
        salesOffices.trigger( 'squareMeter100', 3000000 ); // 輸出:3000000
    </script>
</body>
</html>

模塊間通信

上一節中實現的發佈 — 訂閱模式的實現,是基於一個全局的 Event 對象,我們利用它可以在兩個封裝良好的模塊中進行通信,這兩個模塊可以完全不知道。

比如現在有兩個模塊,a模塊裏面有一個按鈕,每次點擊按鈕之後,b模塊裏的 div中會顯示按鈕的總點擊次數,我們用全局發佈 — 訂閱模式完成下面的代碼,使得 a 模塊和 b 模塊可以在保
持封裝性的前提下進行通信。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="PublishSubscribe.js"></script>
</head>
<body>
    <button id="count">點我</button>
    <div id="show"></div>
    <script>
        var a = (function(){
            var count = 0;
            var button = document.getElementById( 'count' );
            button.onclick = function(){
                Event.trigger( 'add', count++ );
            }
        })();
        var b = (function(){
            var div = document.getElementById( 'show' );
            Event.listen( 'add', function( count ){
                div.innerHTML = count;
            });
        })();
    </script>
</body>
</html>

即發佈和訂閱在不同的兩個模塊裏。

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