JavaScript設計モードの配布-購読モード(オブザーバーモード)-part 2


『JavaScriptデザインモデルと開発実践』読書ノート.
この記事を読む前に、JavaScriptデザインモードのリリース-購読モード(オブザーバーモード)-part 1を参照することをお勧めします.
Part 1では、パブリケーション-サブスクリプションモードとは何かを紹介したとともに、パブリケーション-サブスクリプションモードも実現している.しかし、Part 1で実現した例については、2つの小さな問題がある.
  • 各パブリッシャーオブジェクトにlistenメソッドとtriggerメソッドを追加し、キャッシュリストclientListを追加しました.これは実際にはリソースの浪費です.
  • サブスクライバとパブリッシャーの間には一定の結合性があり、サブスクライバは少なくともパブリッシャーオブジェクトの名前を知ってこそ、スムーズにサブスクライバすることができる.
  • //       A, sgQuestionSystem       (    )
    sgQuestionSystem.listen('questionA', 3, function(questionTitle, content) {
        console.log('           :questionA');
        console.log(' ' + questionTitle + '     ');
        console.log('   :' + content);
    });

    張三が他のものに興味を持っていて、パブリッシャーが同じではない場合、これは張三が別のパブリッシャーオブジェクトを購読する必要があることを意味します.次のようになります.
    //       A  , sgArticleSystem       (    )
    sgArticleSystem.listen('articleA', 3, function(articleTitle, content) { 
        console.log('           :articleA');
        console.log(' ' + articleTitle+ '     ');
        console.log('   :' + content);
    });

    グローバルなパブリケーション-オブジェクトのサブスクリプション
    仲介会社とは何か知っていると思いますが、現実の生活では、購読の要請を仲介会社に渡すだけで、各投稿者も仲介会社を通じて情報を発表するだけです.これにより、メッセージがどのパブリッシャーによって発行されたのかに関心を持つ必要はありません.メッセージが順調に受信されるかどうかに関心を持つ必要があります.もちろん、購読者も発表者もこの仲介会社を知る必要があります.
    同様に、プログラムでは、グローバルなEventオブジェクトを使用して仲介会社の役割を実現することができます.Eventはメッセージセンターとして、購読者とパブリッシャーを連絡します.次のコードを参照してください.
    var Event = (function(){
        var clientList = {}, //     
            listen,
            trigger,
            remove;
        
        listen = function(key, id, fn){
            if(!clientList[key]){
                clientList[key] = [];    //    
            }
            clientList[key].push({      //     id,                
                id: id,                         
                fn: fn                          
            });
        };
        
        trigger = function() {
            var key = Array.prototype.shift.call(arguments),
                fns = clientList[key];
            if(!fns || fns.length == 0) {
                return false;
            }
            for(var i = 0; i < fns.length; i++) {
                fns[i].fn.apply(this, arguments);
            }
        };
        
        remove = function(key, id) {
            var fns = clientList[key];
            
            if(!fns) {                //   key         ,    
                return false;
            }
            
            if(!id) {           //            ,   key       
                fns && (fns.length = 0);
            } else {
                for(var l = fns.length - 1; l >=0; l--) {
                    var _id = fns[l].id;
                    if(_id == id) {
                        fns.splice(l, 1);    //           
                    }
                }
            }
        };
        
        return {
            listen: listen,
            trigger: trigger,
            remove: remove
        }
    })();
    
    Event.listen('questA', 1, function(content) {
        console.log('   :' + content);
    });
    
    Event.trigger('questA', '        ');

    先に購読してから発行しなければなりませんか?
    パブリケーション・サブスクリプション・モードは、サブスクライバがパブリケーションのメッセージを受信する前に、サブスクライバがメッセージをサブスクリプションする必要があります.順序を逆にして、パブリッシャーが先にメッセージをパブリッシュし、それ以前にオブジェクトが購読していなかった場合、このメッセージは宇宙に消えてしまいます.
    実際のプロジェクトでは、パブリッシュ後にサブスクリプションが存在します.例えば、ショッピングモールのウェブサイトでは、ユーザ情報を取得してからユーザナビゲーションモジュールをレンダリングすることができ、ユーザ情報を取得する操作は非同期要求である.リクエストが正常に返された後にイベントをパブリッシュすることができ、その前にイベントを購読したユーザーナビゲーションモジュールがこの情報を受信することができます.
    しかしながら、非同期のためajaxリクエストのイベントは保証されず、ユーザのナビゲーションモジュールがロードされていない(すなわち、メッセージが購読されていない)可能性があり、リクエストが返される(すなわち、メッセージが発行される).そのため、パブリケーション・サブスクリプション・オブジェクトに、パブリケーション・サブスクリプションの前後にサブスクリプションする能力を持たせる必要があります.
    具体的な実装は、後続の最終コードを参照してください.
    グローバルイベントの名前の競合
    グローバルなパブリケーション-サブスクリプションオブジェクトには、メッセージ名とコールバック関数を格納するclientListが1つしかありません.皆さんがサブスクリプション-各種メッセージをパブリッシュします.長い間、イベント名の競合が発生することは避けられません.そのため、Eventオブジェクトにネーミングスペースを作成する機能を提供することもできます.
    最終コードは次のとおりです.
    var Event = (function(){
        var global = this,
            Event,
            _default = 'default';
       
        Event = function(){
            var _listen,
                _trigger,
                _remove,
                _slice = Array.prototype.slice,
                _shift = Array.prototype.shift,
                _unshift = Array.prototype.unshift,
                namespaceCache = {},
                _creat,
                find,
                each = function(ary, fn){
                    var ret;
                    for(var i = 0, l = ary.length; i < l; i++) {
                        var n = ary[i];
                        ret = fn.call(n, i, n);
                    }
                    return ret;
                };
                
                _listen = function(key, fn, cache) {
                    if(!cache[key]) {
                        cache[key] = [];
                    }
                    cache[key].push(fn);
                };
                
                _remove = function(key, cache, fn) {
                    if(cache[key]) {
                        if(fn) {
                            for(var i = cache[key].length; i >=0; i--) {
                                if(cache[key][i] == fn) {
                                    cache[key].splice(i, 1);
                                }
                            }
                        }  else {    //    fn,      
                            cache[key] = [];
                        }    
                    }
                };
                
                _trigger = function() {
                    var cache = _shift.call(arguments),
                          key = _shift.call(arguments),
                          args = arguments,
                          _self= this,
                          ret,
                          stack = cache[key];
                     
                     if(!stack || !stack.length) {
                         return;
                     }
                     
                     return each(stack, function(){
                         return this.apply(_self, args);
                     })
                };
                
                _create = function(namespace) {
                    var namespace = namespace || _default;
                    var cache = {},
                        offlineStack = [],
                        ret = {
                            listen: function(key, fn, last) {
                                _listen(key, fn, cache);
                                if(offlineStack == null) {
                                    return;
                                }
                                if(last == 'last') {
                                    offlineStack.length && offlineStack.pop()();
                                } else {
                                    each(offlineStack, function() {
                                        this();
                                    });
                                }
                                offlineStack = null;
                            },
                            one: function(key, fn, last) {
                                _remove(key, cache);
                                this.listen(key, fn, last);
                            },
                            remove: function(key, fn) {
                                _remove(key, cache, fn);
                            },
                            trigger: function() {
                                var fn,
                                    args,
                                    _self = this;
                                
                                 _unshift.call(arguments, cache);
                                 args = arguments;
                                 fn = function() {
                                     return _trigger.apply(_self, args);
                                 };
                                 if(offlineStack) {
                                     return offlineStack.push(fn);
                                 }
                                 return fn();
                            }
                        };
                        
                        return namespace? (namespaceCache[namespace] ? namespaceCache[namespace]: namespaceCache[namespace] = ret) : ret;
                };
                return {
                    create: _create,
                    one: function(key, fn, last) {
                        var event = this.create();
                        event.one(key, fn, last);
                    },
                    remove: function(key, fn){
                        var event = this.create();
                        event.one(key, fn);
                    },
                    listen: function(key, fn, last){
                        var event = this.create();
                        event.listen(key, fn, last);
                    },
                    trigger: function(){
                        var event = this.create();
                        event.trigger.apply(this, arguments);
                    }
                };
                
        }();
        return Event;
    })();
    
    /**********        ***********/
    Event.trigger('click', 1);
    Event.listen('click', function(a){
        console.log(a)
    });
    
    /**********        ***********/
    Event.create('namespace1').listen('click', function(a){ 
        console.log(a);
    });
    Event.create('namespace1').trigger('click', 1);

    附:JavaScriptデータ構造とアルゴリズムシリーズ:JSスタックJSキュー-優先キュー、循環キュー
    JavaScript設計モードシリーズ:JavaScript設計モードのポリシーモード