第07章 事件處理
7.1 典型用法
當(dāng)事件觸發(fā)時(shí)留储,事件對(duì)象(event對(duì)象)會(huì)作為回調(diào)參數(shù)傳入事件處理程序中。event對(duì)象包含所有和事件相關(guān)的信息丐膝,包括事件的宿主(target)以及其他和事件類型相關(guān)的數(shù)據(jù)。鼠標(biāo)事件會(huì)將其位置信息暴露在event對(duì)象上浑此,鍵盤事件會(huì)將案件信息暴露在event對(duì)象上凛俱,觸屏事件會(huì)將觸摸位置和持續(xù)時(shí)間暴露在event 對(duì)象上。只有提供了所有這些信息原叮,UI才會(huì)正確地執(zhí)行交互篇裁。
在很多場(chǎng)景中团甲,你只是用到了event所提供信息的一小部分身腻,看下面這段代碼脐区。
// 不好的寫法
function handleClick(event) {
"use strick";
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "reveal";
}
盡管這段代碼看起來非常簡(jiǎn)單并且沒什么問題,但實(shí)際上是不好的寫法媒佣,因?yàn)檫@種做法有局限性默伍。
7.2 規(guī)則1:隔離應(yīng)用邏輯
上段實(shí)例代碼的第一個(gè)問題是事件處理程序包含了應(yīng)用邏輯(application logic)。應(yīng)用邏輯是和應(yīng)用相關(guān)的功能性代碼狸剃,而不是和用戶行為相關(guān)的捕捂。上段代碼中,應(yīng)用邏輯是在特定位置顯示一個(gè)彈出框允悦。盡管這個(gè)交互應(yīng)當(dāng)是在用戶點(diǎn)擊某個(gè)特定元素時(shí)發(fā)生,但情況并不總是如此全闷。
將應(yīng)用邏輯從所有事件處理程序中抽離出來的做法是一種最佳實(shí)踐屏鳍,因?yàn)檎f不定什么時(shí)候其他地方就會(huì)觸發(fā)同一段邏輯。比如山涡,有時(shí)你需要用戶鼠標(biāo)移到某個(gè)元素上時(shí)判斷是否顯示彈出框,或者按下鍵盤的某個(gè)鍵時(shí)也作同樣的邏輯判斷系吩。這樣多個(gè)時(shí)間的處理程序執(zhí)行了同樣的邏輯穿挨,而你的代碼卻不小心復(fù)制了多份。
如果對(duì)上段實(shí)例代碼進(jìn)行重構(gòu)贞绵,第一步是將處理彈出框邏輯代碼放入一個(gè)單獨(dú)的函數(shù)中,這個(gè)函數(shù)很可能掛載于為該應(yīng)用定義的一個(gè)全局對(duì)象上母蛛。事件處理程序應(yīng)當(dāng)總是在一個(gè)相同的全局對(duì)象中,因此就有了以下兩個(gè)方法:
var MyApplication = {
handleClick: function (event) {
"use strict";
this.showPopup(event);
},
showPopup: function (event) {
"use strict";
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
}
};
addListener(element, "click", function (event) {
"use strict";
MyApplication.handleClick(event);
});
之前的事件處理程序中包含的所有應(yīng)用邏輯現(xiàn)在轉(zhuǎn)移到了MyApplication.showPopup()方法中。現(xiàn)在MyApplication.handleClick()方法只做一件事情违帆,即調(diào)用MyApplication.showPopup()狈醉。
7.3 規(guī)則2:不要分發(fā)事件對(duì)象
在剝離出應(yīng)用邏輯之后,上段代碼還存在一個(gè)問題班巩,即event對(duì)象被無節(jié)制地分發(fā)。它從匿名的事件處理函數(shù)傳入了MyApplication.handleClick()抑进,然后又傳入了MyApplication.showPopup()。正如上文提到的信殊,event對(duì)象上包含很對(duì)和事件相關(guān)的額外信息,而這段代碼只用到了其中的兩個(gè)而已良拼。
應(yīng)用邏輯不應(yīng)當(dāng)依賴event對(duì)象來正確完成功能仲吏,原因如下:
如果你想測(cè)試這個(gè)方法,你必須重新創(chuàng)建一個(gè)event對(duì)象并將它作為參數(shù)傳入许帐。
最佳的辦法是讓事件事件處理程序使用event對(duì)象來處理事件距芬,然后拿到所有需要的數(shù)據(jù)傳給應(yīng)用邏輯。例如 MyApplication.showPopup() 方法只需要使用兩個(gè)數(shù)據(jù)离斩,x坐標(biāo)和y坐標(biāo):
// 好的寫法
var MyApplication= {
handleClick: function (event) {
"use strict";
this.showPopup(event.clientX, event.clientY);
},
showPopup: function (x, y) {
"use strict";
var pupup = document.getElementById("popup");
pupup.style.left = x;
pupup.style.top = y;
pupup.className = "reveal";
}
};
addListener(element, "click", function (event) {
"use strict";
MyApplication.handleClick(event);
});
這段重寫的代碼中,MyApplication.handleClick() 將 x 坐標(biāo)和 y 坐標(biāo)傳入了MyApplication.showPopup()。代替了之前傳入的事件對(duì)象漾岳』妊颍可以很清晰地看到MyApplication.showPopup()所期望傳入的參數(shù),并且測(cè)試或代碼的任意位置都可以很輕松地直接調(diào)用這段邏輯:
MyApplication. showPopup(10,20);
當(dāng)處理事件時(shí),最好讓事件處理城西稱謂接觸到event對(duì)象的唯一函數(shù)狞悲。事件處理程序應(yīng)當(dāng)在進(jìn)入應(yīng)用邏輯之前針對(duì)event對(duì)象執(zhí)行任何必要的操作,包括阻止默認(rèn)事件或事件冒泡荸恕,都應(yīng)當(dāng)直接包含在事件處理程序中。
var MyApplication = {
handleClick: function (event) {
"use strict";
// 假設(shè)事件支持 DOM Level2
event.preventDefault();
event.stopPropagation();
this.showPopup(event.clientX, event.clientY);
},
showPopup: function (x, y) {
"use strict";
var pupup = document.getElementById("popup");
pupup.style.left = x;
pupup.style.top = y;
pupup.className = "reveal";
}
};
addListener(element, "click", function (event) {
"use strict";
MyApplication.handleClick(event);
});