自定義消息機制研究學習(二)——做一些改動,定製自己的消息機制

天我來嘗試修改一下代碼,以使它更適合我們實際的研發情況。

首先,我們修改一下代碼,讓它可讀性稍微好一點。(原代碼參考上文)

monitor.js
var  monitor= (function(){
        function bind(b){
            var queue = this.__MSG_QS__;
            if (!queue[b]) {
                queue[b] = []
            }
            for (var a = 1, X = arguments.length, Y; a < X; a++) {
                queue[b].push(arguments[a])
            }
        }
        function trigger(Y){
            var queue = this.__MSG_QS__[Y.type];
            if (queue == null) {
                return
            }
            for (var a = 0, X = queue.length; a < X; a++) {
                queue[a].handler(Y)
            }
        }
        return {
            ini: function(X){
                X.__MSG_QS__ = {};
                X.bind = bind;
                X.trigger = trigger;
                return X
            }
        }
})();

好了,現在我們有一個monitor對象了

現在來說說加入我們使用這個對象有可能要應對的情況。



一、將消息直接通知到函數

如果我們要簡單監聽某個對象裏的某個消息,譬如下面代碼中這個對象裏的sendData消息

View Code
var obj1=monitor.ini({sendData:function(){
    this.trigger({type:"sendData",data:"1"});
}});

我們只是想簡單的將這個要發送的數據alert一下,代碼如下:

View Code
obj1.bind("sendData",{handler:function(data){
   alert(data.data);
}});

  

高興的事,我們很快寫完了。我麼可以不要{}呢,也不寫handler麼?

我們改改monitor,讓它能直接將消息發送到函數,對monitor的trigger內的方法做一個簡單的更改,如下:

View Code
function trigger(Y){
            var queue = this.__MSG_QS__[Y.type];
            if (queue == null) {
                return
            }
            for (var a = 0, X = queue.length; a < X; a++) {
               if(queue[a].handler)
               {
                   queue[a].handler(Y)
               }
               else
               {
                   queue[a](Y); 
               }
            }
        }

  

這樣我們就可以直接將消息發送到函數。

當然,這也會給我們帶來一點小小的,因爲加了if語句的性能損失。10000000次trigger的一個測試數據:1076(未修改前):1134(修改後)——單位毫秒


一個極端點的更改,我們只想把消息傳給函數,不傳給對象,那麼修改如下:

monitor 只傳函數
var  monitor= (function(){
        function bind(b){
            var queue = this.__MSG_QS__;
            if (!queue[b]) {
                queue[b] = []
            }
            for (var a = 1, X = arguments.length, Y; a < X; a++) {
               queue[b].push(arguments[a])
            }
        }
        function trigger(Y){
            var queue = this.__MSG_QS__[Y.type];
            if (queue == null) {
                return
            }
            for (var a = 0, X = queue.length; a < X; a++) {
                   queue[a](Y); 
            }
        }
        return {
            ini: function(X){
                X.__MSG_QS__ = {};
                X.bind = bind;
                X.trigger = trigger;
                return X
            }
        }
})();

這樣,我們只能bind函數了,這樣的方式在一些簡單的應用中效果也不錯,比如我們用jQuery的bind方法就可以實現很多我們要的效果,爲了實現bind函數內this指向消息源頭,這裏使用call方法就可,代碼:

View Code

這樣,我們可以基於一個或者數個複雜的對象做一些擴展開發,就像基於dom的click等事件來實現我們想要的效果一樣簡單。

但如果涉及多個對象直接互相傳遞消息,只bind到函數就有點限制。如果不是特殊的需求,不建議用這種方式,最好bind到對象,兼容bind到對象和函數,也會讓我們少敲一些handler,因此也是個不錯的選擇




二、new的對象如何綁定monitor

當我們準備用js面向對象開發時,我們幹:

View Code
function base()
{}

var obj=new base();

那麼我們想要在new出來對象上使用monitor模式,少一點使用,我們可以monitor.ini(obj);

那麼如果大量類似對象要使用monitor模式呢?譬如

View Code
function Person(name)
{
    this.name=name;
    this.sayHello=function()
    {
        this.trigger({type:"say",msg:"hi,我是"+this.name})
    }
}

  我們要創建很多的對象,然後調用他們的sayHello,假設是很多人對不同的對象說話的場景,我們創建一個Person對象就要monitor.ini一下,這種辦法很笨

假設你不想修改monitor的代碼,你可以這樣:

monitor.ini(Person.prototype);

  如果你確實想簡單寫成如下:

monitor.ini(Person);

  那麼只好修改修改monitor代碼了:

首先是ini

View Code
ini: function(X){
                if(Object.prototype.toString.call(X)=="[object Function]")
                {
                    var proto=X.prototype;
                    proto.bind = bind;
                    proto.trigger = trigger;                    
                }
                X.bind = bind;
                X.trigger = trigger;
                return X
            }

 我去掉了__MSG_QS__ 這個的初始化,因爲如果在prototype上綁定__MSG_QS__ 屬性的話,每一個bind都會bind到所有對象上,這不是我們的本意,就例如我們希望的是每一個Person說的話,只能由聽他說話的人收聽到。實現這樣的效果還需要在bind,trigger時做一些修改,如下:

bind
trigger
function trigger(Y){
            var qs=this.__MSG_QS__ || {};
            var queue= qs[Y.type] || []; 
            for (var a = 0, X = queue.length; a < X; a++) {
                   if(queue[a].handler)
                   {
                        queue[a].handler(Y)
                   }
                   else
                   {
                        queue[a].call(this,Y);; 
                   }
            }
        }

  

 

 ***PS:我把if (queue == null) {return }也去掉了




三、如何綁定類消息

這裏的類消息是這樣的一種需求,比如接上例,我們要用一個logger記錄所有Person講的話(sayHello()),難道我們創建一百個Person,就要調用一百次bind麼?假如只有一處代碼才能new Person()那還好說,不會增加我們多少的代碼量。但你的new Person已經灑落到代碼各處,到處都是。OMG。怎麼辦?

首先,再剽竊一個jQuery的命名:live,在monitor中加入live代碼如下:

live
function live(b)
        {
            var queue = this.prototype.__STATIC_MSG_QS__;
            if (!queue[b]) {
                queue[b] = []
            }
            for (var a = 1, X = arguments.length, Y; a < X; a++) {
                queue[b].push(arguments[a])
            }
        }

這段代碼與bind區別不大,唯一的區別是它這裏使用了this.prototype.__STATIC_MSG_QS__ 而不是this.__MSG_QS__ 我們用__STATIC_MSG_QS__ 來存儲類級別的消息隊列

所以它是面向function對象的,把ini修改如下

ini
return {
            ini: function(X){
                if(Object.prototype.toString.call(X)=="[object Function]")
                {
                    var proto=X.prototype;
                    proto.__STATIC_MSG_QS__={};
                    proto.bind = bind;
                    proto.trigger = trigger;
                    X.live=live; 
                    
                }
                X.bind = bind;
                X.trigger = trigger;
                return X
            }
        }

  如果ini的是function,我們就再function上面綁定了live方法,並且在prototype上增加了__STATIC_MSG_QS__ 

我們還需要修改一下trigger

trigger
function trigger(Y){
            var queue =[];
            var qs=this.__MSG_QS__ || {}; 
            var sqs=this.__STATIC_MSG_QS__|| {}; 
            queue= queue.concat(qs[Y.type] || []);
            queue= queue.concat(sqs[Y.type] || []);
            for (var a = 0, X = queue.length; a < X; a++) {
               if(queue[a].handler)
               {
                   queue[a].handler(Y)
               }
               else
               {
                   queue[a].call(this,Y); 
               }
            }
        }

  增加了一些trigger的負擔,queue不再直接指向this.__MSG_QS__中獲取(queue=this.__MSG_QS__會使兩者指向同一內存地址),因爲這樣如果修改queue變量會直接修改掉__MSG_QS__隊列中的值,在這裏用了兩次concat分別拷貝了對象消息監聽者和類消息接聽者。

ok,現在我們可以一下就監聽所有Person對象的消息了,代碼如下:

Person.live("say",{handler: function(data){
 //logger
});

  搞定,準備洗洗睡吧!


想要使用類似monitor的人,根據自己的實際需求定製一下。比如你想在監聽函數裏調用消息源,可以把trigger中queue[a].handler(Y)修改爲queue[a].handler(Y,this)等等

修改後的monitor

View Code
var  monitor= (function(){
        function bind(b){
            var queue = this.__MSG_QS__=this.__MSG_QS__ || {};
            if (!queue[b]) {
                queue[b] = []
            }
            for (var a = 1, X = arguments.length, Y; a < X; a++) {
                queue[b].push(arguments[a])
            }
        }
        
        function live(b)
        {
            var queue = this.prototype.__STATIC_MSG_QS__;
            if (!queue[b]) {
                queue[b] = []
            }
            for (var a = 1, X = arguments.length, Y; a < X; a++) {
                queue[b].push(arguments[a])
            }
        }
        
        function trigger(Y){
            var queue =[];
            var qs=this.__MSG_QS__ || {}; 
            var sqs=this.__STATIC_MSG_QS__|| {}; 
          queue= queue.concat(qs[Y.type] || []);
            queue= queue.concat(sqs[Y.type] || []);
            for (var a = 0, X = queue.length; a < X; a++) {
               if(queue[a].handler)
               {
                   queue[a].handler(Y,this)
               }
               else
               {
                   queue[a].call(this,Y,this); 
               }
            }
        }
        return {
            ini: function(X){
                if(Object.prototype.toString.call(X)=="[object Function]")
                {
                    var proto=X.prototype;
                    proto.__STATIC_MSG_QS__={};
                    proto.bind = bind;
                    proto.trigger = trigger;
                    X.live=live; 
                    
                }
                X.bind = bind;
                X.trigger = trigger;
                return X
            }
        }
})();

它多瞭如下一些特性:

1. 可以直接用一個函數偵聽消息

2. 可以將function註冊爲monitor,然後所有function new出來的對象都將自動綁定了monitor

3. 爲function增加了live方法,可以方便偵聽所有new對象的事件 

4. 將消息源作爲第二個參數傳給了方法。


附:

這只是簡單的一些修改擴展,如果你沒有這些需要,簡單原始的monitor就非常簡潔高效,足夠應付一些簡單的建模。

實際中我用的類似的monitor模式,不是隻有ini的一個{ini:function()}對象,而是一個有bind,trigger,unbind,live,die等方法的function對象,使用繼承的方式(比如jQuery.extend)來爲{}或new的對象綁定monitor的模式。因此增加了許多的條件判斷,性能上要比本文的monitor差一些。

這裏的代碼都是寫本文時隨手寫的,難免有誤,歡迎指正
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章