今天我來嘗試修改一下代碼,以使它更適合我們實際的研發情況。
首先,我們修改一下代碼,讓它可讀性稍微好一點。(原代碼參考上文)
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差一些。
這裡的代碼都是寫本文時隨手寫的,難免有誤,歡迎指正。
預告:
下一篇,我會分享一下基於最後這個版本monitor的插件模式的嘗試,熱烈歡迎你的關注。