程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> JSP編程 >> 關於JSP >> js自定義消息機制研究學習(四)之雜七雜八

js自定義消息機制研究學習(四)之雜七雜八

編輯:關於JSP

終於要寫完了~~^_^,期間給同事做了一次培訓,寫一次,講一次的好處是,再次加深了自己對於消息、事件以及觀察者模式的理解。

 

對我來說,講清楚比寫代碼要難上很多。

 

這裡分享一些與消息機制相關的一些雜七雜八的內容。

 

 

 

一、可測試的代碼

 

早些時候,我向銳同學描述我的js程序結構,他問了我一個問題:你的js代碼可測麼?

 

我蒙了~雖然一直關注敏捷,一直也向往測試驅動開發,但還從沒想過js代碼的可測試(當然,也有測試,但基本上整測加局部測試),沒有想過js的測試驅動。

 

當時,我遲疑了一會,才說應該是可測的。

 

寫完上一篇文章(原諒我,覺得太簡單,直接寫的,忘了測試驅動),回頭看了看,還好,基於消息的代碼確實可以做到可測。

 

比如:

 

Animal

function Animal(config){    config=config || {};    var othis=this;    this.name=config["name"] || "Anonymous";    this.x=config["x"] || 0;    var toward=1;//右:1,左-1    var __timeout=0;    var __speed=5;    //說    this.say=function(str)    {        this.trigger({type:"say",msg:str});        return str;    }    //停    this.stop=function()    {        clearTimeout(__timeout);         this.trigger({type:"stop",x:this.x});        return this.x;    }    //跑動    this.run=function(speed)    {        __speed=speed || __speed;        this.x+=__speed*toward;        __timeout=setTimeout(function(){ othis.run();},100);        this.trigger({type:"run",x:this.x,toward:toward,speed:__speed});        return {x:this.x,toward:toward,speed:__speed};    }    //向左轉    this.turnLeft=function()    {        toward=-1;        this.trigger({type:"turn",toward:toward});        return toward;    }    //向右轉    this.turnRight=function()    {        toward=1;        this.trigger({type:"turn",toward:toward});        return toward;    }}方法都是有返回值,這樣便於我們做一些單元測試,除了單元測試,我們還要做一些基於消息的機制的測試,構造一個偽對象去偵聽Animal對象發送的消息

 

但這個Animal並不完全,還有一些不可測的,比如toward,它是完全封閉在內部的一個變量,你想要知道Animal的對象前進的方向,就有些困難。

 

不過這主要源於Animal這個類得不完全,但我們不能為了訪問toward而直接把它修改為this.toward暴露出來,這樣別人有可能賦一個錯誤的值:this.toward=100;仔細看一下代碼,公開toward讓人可以隨別賦值是很危險的一件事情。好的方式,寫一個getToward()方法。

 

一種難以測試的實現是示例這樣寫的:

 

Logger

///記錄器function Logger(){     var dom=document.getElementById("log");      var log=function(str)      {          var time=new Date();          this.dom.innerHTML+="<br/>"+str+'<span style="color:gray">('+time.getHours()+":"+time.getMinutes()+":"+time.getSeconds()+")</span>";      };      this.handler=function(data,p){          switch(data.type)          {              case "say":                  this.log(p.name+"  說:"+data.msg);                  break;              case "stop":                  this.log('<span style="color:green">'+p.name+" 停在了"+data.x+'</span>');                  break;              case "turn":                  this.log(p.name+" 轉向了"+(data.toward==1?"右":"左"));                  break;          }            }; }Logger對象只暴露了一個handler方法,它寫死了dom。

 

當然它在示例運行中會很好的執行自己的職責。為了測試它,我們首先保證頁面上要有一個id為log的dom元素,還要偽造一個消息對象,如Animal對象去給它發消息。這讓我們又一種整體測試的感覺。

 

這不是一個好的示例。

 

 

總體而言,消息的密閉性會給測試帶來一些麻煩。實際中,我們一個函數的調用可能會觸發很多個消息,而不只是一個。而這些消息名又灑落在了代碼的各處。除非你仔細的閱讀代碼,否則很可能會遺漏消息。

 

像Animal一樣,盡量做到一個方法值觸發一個消息,或者相反、相關聯的消息。如果是一些大的對象,把消息名羅列在對象開始前得注釋代碼中,也便於他們維護調試。

 

 

 

二、冒泡的消息

 

在剛開始的時候,我曾實現過消息的冒泡,就像是內部a標簽的click事件會冒泡到外部div一樣。

 

消息的冒泡示例:

 

a.bind("test",b) b.bind("test",c) c.bind("test",d)

 

如果你實現了冒泡,a的test消息會沿著a->b->c->d的路徑一直傳送到d

 

簡單的實現呢,就是每個對象的handler函數,直接trigger一下傳進來的消息,這樣的方式並沒有實現自動化。一個簡單改動如下:

 

View Code

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)                   if(queue[a].trigger)                   {                          queue[a].trigger(Y);                    }               }               else               {                   queue[a].call(this,Y,this);                }            }        } 重點看一下這一個改動:

 

if(queue[a].trigger)     {            queue[a].trigger(Y);     }如果發現觀察者是一個monitor模式對象,那麼調用它的trigger

 

這樣,我們的消息就可以實現冒泡了。我們也可以為對象添加一個屬性,標示對象是否允許冒泡,也可以再添加stopPropagation一個來阻止冒泡。

 

 

 

關於消息的冒泡,我的建議是謹慎使用。自定義對象不同於dom,dom是有層次結構的,dom只對父元素冒泡。

 

自定義對象是沒有層次的(除非強制),有時我們甚至可以讓對象自己監聽自己的消息,很多時候,有很多對象偵聽你的消息。實質的講,此時的消息流,並不像冒泡,而是會有分支,在處理不好的情況下會有閉環,會有類似遞歸一樣的自調用,自調用、閉環的情況都會引起死循環。

 

當然,你也可以使用一個字典來存儲消息已經傳遞過的對象(注入到消息體內,或作為trigger另一個參數),防止閉環。但這樣又會使你的monitor代碼增加很多的處理。

 

 

 

 

 

 

三、異步的消息

 

之前,我所舉的例子、代碼,都是同步消息,消息調用,監聽函數就會執行,並且只有所有的監聽函數執行完畢,trigger的調用才結束。

 

 

 

 

比如:

 

obj.trigger({type:"test1"});

 

obj.trigger({type:"test2"});

 

test2的消息一定是在test1消息調用結束後才調用。

 

異步的消息,只是我的一個想法,利用setTimeout函數,用時間片得形式,一次只調用一個監聽者,那麼在消息源對象調用trigger方法,trigger很快可以執行完畢,而真正的消息處理呢,是放在了時間片中,慢慢的處理。

 

目前我沒有用到需要異步消息處理的需求,對於設想的方案,沒有做過測試。有興趣的人可以自己研究一下。

 

估計有的js框架有類似的功能,可惜我沒有研究過。

 

 

 

 

 

四、避免重復注冊及注銷觀察者

 

 

 

 

如果你a.bind("test",b)兩次,會發生什麼情況?

 

a對象的test的事件,會很忠實地調用兩次b。

 

為了避免重復,直接的方式就是一遍列表,判斷一下b是否存在,這樣的效率很低下。一種方式是為需要bind的對象,增加一個唯一標示,在monitor內增加一個函數(或者在全局增加一個函數),代碼如下

 

var  monitor= (function(){//……       var __guid=0;       function guid(){               return ++__guid;        }         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++) {                var _guid=arguments[a].__guid=arguments[a].__guid || guid();                if( queue[b][_guid])                       queue[b][_guid]=arguments[a];            }        }//……})();相應的trigger裡也要做一些相應的改變(以及live),這裡不再給出代碼,有興趣的自己實現

 

注銷是bind的反操作,如果你沒有使用為對象賦唯一標示的方式,你需要用循環去判斷對象是否在觀察者隊列中,如果在則從隊列中移除。如果使用唯一標示,操作比較簡單,使用delete即可。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved