本節稍稍深入地討論關於事件處理的話題,如果你對模式、閉包和物件導向等概念還不太理解,不妨暫且等閱讀完相關內容之後再回過頭來閱讀它,相信你會有很大收穫。
1 事件處理模式
在程式設計領域,“事件處理”是一種模式,當一個對象受外部影響而改變狀態時,通過訊息的方式將這個狀態改變通知給這個對象或者相關聯的某個對象,讓它執行對應的動作,這就是事件處理的基本原理。負責通知狀態改變的對象被稱作“訊息”,而執行響應動作的屬性則被稱作“事件代理”。
例如下面就是一個簡單的事件處理模式的應用:
function dispatchEvent(owner, eventType, eventArgs)
{
if(owner && owner["on"+eventType])
setTimeout(function(){owner["on"+eventType](eventArgs)}, 1);
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i < len; i++)
{
ret.push(randomSignal());
}
return ret;
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
this.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
this.bufferSize = function()
{
return obl;
}
this.input = function(serials)
{
for(var i = 1; i < serials.length; i++)
{
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
dispatchEvent(this, "signalchange",
{input:serials, time:time, buffer:buffer.slice(0)});
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
alert(diff10.readBuffer());
diff10.onsignalchange = function(eventArgs)
{
alert(eventArgs.time);
}
diff10.input(inputSerials);
在上面的例子中,函數dispatchEvent負責指派事件,onsignalchange是事件代理,在這個差分系統diff10中,當輸入訊號的電平發生變化(從0到1或者從1到0)時,觸發相應的事件onsignalchange,並且將當前輸入訊號、時序和當前輸出緩衝作為事件參數傳入事件處理常式。
diff10.onsignalchange = function(eventArgs)
{
alert(eventArgs.time);
}
是程式員指定的事件處理常式,在這裡我們列印出輸入電平發生變化時的輸入訊號時序。
2 使用者事件介面的定義
前面的例子中,我們僅僅定義了一個用來指派事件的函數dispatchEvent,但它也可以看作是一個完整的使用者事件介面,現在我們回顧這個函數,弄明白它究竟做了什麼樣的事情:
function dispatchEvent(owner, eventName, eventArgs)
{
if(owner && owner["on"+eventName])
setTimeout(function(){owner["on"+eventName](eventArgs)}, 1);
}
這個函數接收三個參數,它的第一個參數是一個對象,指定了這個事件的“所有者”,即這個事件是由誰接收和負責處理的。在上面的例子中,這個owner是Differ對象本身即
dispatchEvent(this, "signalchange", {input:serials, time:time, buffer:buffer});
傳入的owner參數是this,實際上事件模式允許其他類型作為事件指派的所有者,尤其在一些特定的模式,通常事件的發起者和事件的接收者可以不是同一個對象。在4小節介紹的觀察者模式中可以看到這一點。
第二個參數是一個表示事件類型的字串,它決定了事件代理的名稱,根據事件模型的規範,事件代理的名稱為”on”+事件類型,例如上面例子中,事件類型為signalchange,對應的事件代理為onsignalchange。
第三個參數是一個事件參數對象,它決定了傳遞給事件接收者的參數,在上面的例子中,它傳遞了input、time和buffer三個屬性,分別代表發生事件時的當前輸入序列、時序以及輸出緩衝的值。
dispatchEvent函數本身的內容很簡單,它只是確保調用接收者的事件代理,並將事件參數正確傳入這個事件代理。至於事件代理是如何處理事件參數的,它並不關心。
3 事件代理和事件註冊
在事件處理模式中,為事件代理指定事件處理函數的過程被稱為事件註冊。在上面的例子中,diff10.onsignalchange是極其簡單的事件代理,它的事件註冊過程也極為簡單——採用直接賦值的方式來完成。
事實上根據設計的不同,事件代理可以有更加複雜的註冊方式,例如DOM-level-2的addEventListener和removeEventListener,我們也可以實作類別似的事件註冊方法,以支援為一個事件代理註冊多個事件事件處理方法。為了實現它,我們完善事件介面,修改上面的例子如下:
function EventManager(owner)
{
owner = owner || this;
this.dispatchEvent = function(eventType, eventArgs)
{
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
if(owner && events)
{
for(var i = 0; i < events.length; i++)
{
setTimeout(
(function(i){return function(){events[i](eventArgs)}
})(i), 1
);
}
}
}
this.addEventListener = function(eventType, closure)
{
if(owner["on"+eventType] == null)
{
owner["on"+eventType] = [];
}
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
events.push(closure);
}
this.removeEventListener = function(eventType, closure)
{
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
for(var i = 0; i < events.length; i++)
{
if(events[i] == closure)
events.splice(i, 1);
}
}
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i < len; i++)
{
ret.push(randomSignal());
}
return ret;
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
EventManager.call(this); //apply EnventManager Component.
this.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
this.bufferSize = function()
{
return obl;
}
this.input = function(serials)
{
for(var i = 1; i < serials.length; i++)
{
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
this.dispatchEvent("signalchange",
{input:serials, time:time, buffer:buffer.slice(0)});
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
alert(diff10.readBuffer());
var eventHandler1 = function(eventArgs){
alert(eventArgs.time);
}
var eventHandler2 = function(eventArgs){
alert(eventArgs.buffer);
}
diff10.addEventListener("signalchange",eventHandler1);
diff10.addEventListener("signalchange",eventHandler2);
diff10.input(inputSerials);
diff10.removeEventListener("signalchange",eventHandler1);
在上面的例子裡,我們建立了一個EventManager類型,為它定義了三個對象方法,dispatchEvent方法和前面那個例子很類似,是用來指派事件的,而另外的addEventListener和removeEventListener則是用來註冊和登出事件處理函數。
在Differ類型中,我們通過EventManager.call(this);將EventManager類型的執行個體運用到Differ原型中(關於這個問題的深層機制,留待以後再進行詳細討論)。然後調用this.dispatchEvent來指派事件。
在為Differ執行個體的onsignalchange事件代理註冊事件時,你會發現它和標準的DOM事件模型非常類似:
diff10.addEventListener("signalchange",eventHandler1);
diff10.addEventListener("signalchange",eventHandler2);
diff10.removeEventListener("signalchange",eventHandler1);
運行過這個例子,你會發現一個有趣的地方,就是diff10.input(inputSerials);觸發的事件並沒有執行eventHandler1和eventHandler2,而是只執行了eventHandler2,原因是:
diff10.removeEventListener("signalchange",eventHandler1);
先於事件的觸發被執行,這是因為事件機制是一種“非同步回調”機制,關於同步和非同步問題,我們以後討論。
4 標準模式:事件指派和接收
在事件處理模式中,事件的指派者負責發出訊息,事件的接收者負責處理訊息。在前面的例子裡,它們是由同一個對象(Differ)完成的。
然而,事實上,事件處理模式中,並不要求訊息的發送和接收由同一個對象完成,在某些模式中,它們是不同的對象,其中最常見的一種是“觀察者”模式,下面將差分系統的例子改寫為觀察者模式:
function dispatchEvent(owner, eventType, eventArgs)
{
if(owner && owner["on"+eventType])
setTimeout(function(){owner["on"+eventType](eventArgs)}, 1);
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i < len; i++)
{
ret.push(randomSignal());
}
return ret;
}
function DifferObserver(differ)
{
this.differ = differ;
differ.setObserver(this);
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
var observer = null;
this.input = function(serials)
{
for(var i = 1; i < serials.length; i++)
{
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
dispatchEvent(observer, "signalchange", {sender:this, input:serials, time:time, buffer:buffer.slice(0)});
}
}
this.setObserver = function(obs)
{
observer = obs;
observer.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
observer.bufferSize = function()
{
return obl;
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
var diffObs = new DifferObserver(diff10);
alert(diffObs.readBuffer());
diffObs.onsignalchange = function(eventArgs)
{
if(diff10 == eventArgs.sender)
alert(eventArgs.time);
}
diff10.input(inputSerials);
上面例子中的事件指派者是Differ類型,而事件接收者則是DifferObserver類型,所以事件註冊的代理是DifferObserver的屬性,在發送的事件參數中,我們增加了一個屬性sender,它引用事件的實際發送對象
原文:http://bbs.51js.com/thread-69808-1-1.html by 月影