本章內(nèi)容
- 理解事件流
- 使用事件處理程序
- 不同的事件類型
JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的驹尼。事件话原,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間。
13.1 事件流
事件流描述的是從頁(yè)面中接收事件的順序垢揩。IE 的事件流是事件冒泡流通贞,而 Netscape Communicator 的事件流失事件捕獲流。
13.1.1 事件冒泡
IE 的事件流叫做事件冒泡仁连,即事件開始時(shí)由最具體的元素接收蓝角,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)。以下面的 HTML 頁(yè)面為例:
<!DOCTYPE html>
<html>
<head>
<title>event bubbling example</title>
</head>
<body>
<div>click me</div>
</body>
</html>
如果單擊了頁(yè)面中的<div>
元素饭冬,那么這個(gè)click
事件就會(huì)按照如下順序傳播:
<div>
<body>
<html>
document
所有現(xiàn)代瀏覽器都支持事件冒泡使鹅,但在具體實(shí)現(xiàn)上還是有一些差別。
13.1.2 事件捕獲
事件捕獲的思想是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件昌抠,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件患朱。事件捕獲的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它。前述例子會(huì)按下列順序觸發(fā)click
事件炊苫。
document
<html>
<body>
<div>
雖然事件捕獲是 Netscape Communicator 唯一支持的事件流模型裁厅,但 其他主流瀏覽器目前也都支持這種事件流模型。
建議放心地使用事件冒泡侨艾,在有特殊需要時(shí)再使用事件捕獲执虹。
13.1.3 DOM 事件流
“DOM2級(jí)事件”規(guī)定的事件流包括三個(gè)階段:事件捕獲階段、處于目標(biāo)階段和事件冒泡階段唠梨。首先發(fā)送的是事件捕獲袋励,為截獲事件提供了機(jī)會(huì)。然后是實(shí)際的目標(biāo)接收到事件。最后一個(gè)階段是冒泡階段茬故,可以在這個(gè)階段對(duì)事件做出響應(yīng)盖灸。
在 DOM 事件流中,實(shí)際的目標(biāo)(<div>
元素)在捕獲階段不會(huì)接收到事件磺芭。也就是說(shuō)赁炎,事件從document
到<html>
再到<body>
后就停止了。下一個(gè)階段是“處于目標(biāo)”階段钾腺,于是事件在<div>
上發(fā)生徙垫,并在事件處理中被看成冒泡階段的一部分。然后垮庐,冒泡階段發(fā)生松邪,事件又傳播回文檔坞琴。
多數(shù)支持 DOM 事件流的瀏覽器都實(shí)現(xiàn)了一種特定的行為:即使“DOM2 級(jí)事件”規(guī)范明確要求捕獲階段不會(huì)涉及事件目標(biāo)哨查,但高版本瀏覽器都會(huì)在捕獲階段觸發(fā)事件對(duì)象上的事件。結(jié)果剧辐,就是有兩個(gè)機(jī)會(huì)再目標(biāo)對(duì)象上面操作事件寒亥。
13.2 事件處理程序
事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作。諸如click
荧关、load
和mouseover
溉奕,都是事件的名字。而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(或事件偵聽器)忍啤。事件處理程序的名字以“on”開頭加勤,因此click
事件的事件處理程序就是onclick
,load
事件的事件處理程序就是onload
同波。為事件指定處理程序的方式有好幾種鳄梅。
13.2.1 HTML 事件處理程序
某個(gè)元素支持的每種事件,都可以使用一個(gè)與相應(yīng)事件處理程序同名的 HTML 特性來(lái)指定未檩。這個(gè)特性的值應(yīng)該是能夠執(zhí)行的 JavaScript 代碼戴尸。例如:
<input type="button" value="click me" onclick ="alert('clicked')" />
在 HTML 中定義的事件處理程序可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁(yè)面其他地方定義的腳本冤狡,如下例所示:
<script type="text/javascript">
function showMessage() {
alert('hello world!');
}
</script>
<input type="button" value="click me" onclick="showMessage()" />
事件處理程序中的代碼在執(zhí)行時(shí)孙蒙,有權(quán)訪問全局作用域中的任何代碼。
這樣指定事件處理程序具有一些獨(dú)到之處悲雳。首先挎峦,這樣會(huì)創(chuàng)建一個(gè)封裝著元素屬性值的函數(shù)。這個(gè)函數(shù)中有一個(gè)局部變量event
合瓢,也就是事件對(duì)象浑测。
不過,在 HTML 中指定事件處理程序有兩個(gè)缺點(diǎn)。首先迁央,存在一個(gè)時(shí)差問題掷匠。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件岖圈。
另一個(gè)缺點(diǎn)是讹语,這樣擴(kuò)展事件處理程序的作用域在不同瀏覽器中會(huì)導(dǎo)致不同結(jié)果。
最后一個(gè)缺點(diǎn)是 HTML 與 JavaScript 代碼緊密耦合蜂科。故推薦使用 JavaScript 指定事件處理程序顽决。
13.2.2 DOM0 級(jí)事件處理程序
通過 JavaScript 指定事件處理程序的傳統(tǒng)方式,就是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩缘枷弧_@種為事件處理程序賦值的方法是在第四代 Web 瀏覽器中出現(xiàn)的才菠,而且至今仍然為所有現(xiàn)代瀏覽器所支持。原因一是簡(jiǎn)單贡定,二是具有跨瀏覽器的優(yōu)勢(shì)赋访。要使用 JavaScript 指定事件處理程序,首先必須取得一個(gè)要操作的對(duì)象的引用缓待。
每個(gè)元素都有自己的事件處理程序?qū)傩则镜ⅲ@些屬性通常全部小寫,例如onclick
旋炒。將這種屬性的值設(shè)置為一個(gè)函數(shù)步悠,就可以指定事件處理程序,如下所示:
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert('clicked');
};
使用 DOM0 級(jí)方法指定的事件處理程序被認(rèn)為是元素的方法瘫镇。因此鼎兽,這時(shí)候的事件處理程序是在元素的作用域中運(yùn)行;換句話說(shuō)铣除,程序中的this
引用當(dāng)前元素谚咬。如下例:
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert(this.id); //"myBtn"
};
以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理。
可以刪除通過 DOM0 級(jí)方法指定的事件處理程序通孽,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為null
即可序宦。
btn.onclick = null; //刪除事件處理程序
13.2.3 DOM2 級(jí)事件處理程序
“DOM2級(jí)事件”定義了兩個(gè)方法,用于處理指定和刪除事件處理程序的操作:addEventListener()
和removeEventListener()
背苦。所有 DOM 節(jié)點(diǎn)中都包含這兩個(gè)方法互捌,并且它們都接受 3 個(gè)參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值行剂。最后這個(gè)布爾值參數(shù)如果是true
秕噪,表示在捕獲階段調(diào)用事件處理程序;如果是false
厚宰,表示在冒泡階段調(diào)用事件處理程序腌巾。
要在按鈕上為click
事件添加事件處理程序遂填,可以使用下列代碼:
var btn = document.getElementById("myBtn");
btn.addEventListener('click', function () {
alert(this.id);
}, false);
與 DOM0 級(jí)方法一樣,這里添加的事件處理程序也是在其依附的元素的作用域中運(yùn)行澈蝙。使用 DOM2 級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序吓坚。如下例:
var btn = document.getElementById('myBtn');
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.addEventListener("click", function () {
alert("Hello world!");
}, false);
這里為按鈕添加了倆事件處理程序。這兩個(gè)事件處理程序會(huì)按照添加它們的順序觸發(fā)灯荧,因此首先會(huì)顯示元素的 ID礁击,其次會(huì)顯示“Hello world!
”消息。
通過addEventListener()
添加的事件處理程序只能使用removeEventListener()
來(lái)移除逗载;移除時(shí)傳入的參數(shù)與添加處理程序時(shí)使用的參數(shù)相同哆窿。這也意味著通過addEventListener()
添加的匿名函數(shù)將無(wú)法移除,如下例:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
//這里省略了其他代碼
btn.removeEventListener("click", function() {
alert(this.id); //沒有用
}, false)
再看看下例:
var btn = document.getElementById("myBtn");
var handler = function () {
alert(this.id);
};
btn.addEventListener("click", handler, false);
//這里省略了其他代碼
btn.removeEventListener("click", handler, false); //有效厉斟!
大多數(shù)情況下挚躯,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器擦秽。
13.2.4 IE 事件處理程序
IE 實(shí)現(xiàn)了與 DOM 中類似的兩個(gè)方法:attachEvent()
和detachEvent()
码荔。這兩個(gè)方法接受相同的兩個(gè)參數(shù):事件處理程序名稱與事件處理程序函數(shù)。由于 IE8 及更早版本只支持事件冒泡号涯,所以通過attachEvent()
添加的事件處理程序都會(huì)被添加到冒泡階段目胡。
要使用attachEvent()
為按鈕添加一個(gè)事件處理程序锯七,可以使用以下代碼链快。
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function() {
alert("clicked");
});
注意,attachEvent()
的第一個(gè)參數(shù)是"onclick"
眉尸。
在 IE 中使用attachEvent()
與使用 DOM0 級(jí)方法的主要區(qū)別在于事件處理程序的作用域域蜗。在使用 DOM0 級(jí)方法的情況下,事件處理程序會(huì)在其所屬元素的作用域內(nèi)運(yùn)行噪猾;在使用attachEvent()
方法的情況下霉祸,事件,事件處理程序會(huì)在全局作用域中運(yùn)行袱蜡,因此this
等于window
丝蹭。看下例:
var btn = document.getElementById('myBtn');
btn.attachEvent("onclick", function () {
alert(this === window); //true
});
var btn = document.getElementById('myBtn');
btn.attachEvent("onclick", function () {
alert("clicked");
});
btn.attachEvent("onclick", function () {
alert("Hello world!");
});
與 DOM 方法不同的是坪蚁,這些事件處理程序不是以添加它們的順序執(zhí)行奔穿,而是以相反的順序被觸發(fā)。
使用attachEvent()
添加的事件可以通過detachEvent()
來(lái)移除敏晤。
var btn = document.getElementById("myBtn");
var handler = function () {
alert("clicked");
};
btn.attachEvent("onclick", handler);
//這里省略了其他代碼
btn.detachEvent("onclick", handler);
13.2.5 跨瀏覽器的事件處理程序
為了以跨瀏覽器的方式處理事件贱田,不少開發(fā)人員會(huì)使用能夠隔離瀏覽器差異的 JavaScript 庫(kù),還有一些會(huì)自己開發(fā)最合適的事件處理的方法嘴脾。自己編寫代碼其實(shí)也不難男摧,只要恰當(dāng)?shù)厥褂媚芰z測(cè)即可。要保證處理事件的代碼能在大多數(shù)瀏覽器下一致地運(yùn)行,只需關(guān)注冒泡階段耗拓。
第一個(gè)要?jiǎng)?chuàng)建的方法是addHandler()
拇颅,它的職責(zé)是視情況分別使用 DOM0 級(jí)方法、DOM2 級(jí)方法或 IE 方法來(lái)添加事件乔询。這個(gè)方法屬于一個(gè)名叫EventUtil
的對(duì)象蔬蕊,本書將使用這個(gè)對(duì)象來(lái)處理瀏覽器間的差異。addHandler()
方法接受 3 個(gè)參數(shù):要操作的元素哥谷、事件名稱和事件處理程序岸夯。
與addHandler()
對(duì)應(yīng)的方法是removeHandler()
,它也接受相同的參數(shù)们妥。這個(gè)方法的職責(zé)是移除之前添加的事件處理程序猜扮,默認(rèn)采用 DOM0 級(jí)方法。
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on"+type, handler);
} else {
element["on" + type] = handler;
}
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on"+type] = null;
}
}
可以像下面這樣使用EventUtil
對(duì)象:
var btn = document.getElementById("myBtn");
var handler = function () {
alert("clicked");
};
EventUtil.addHandler(btn, "click", handler);
//這里省略了其他代碼
EventUtil.removeHandler(btn, "click", handler);
addHandler()
和removeHandler()
沒有考慮到所有的瀏覽器問題监婶,例如在 IE 中的作用域問題旅赢。
13.3 事件對(duì)象
在觸發(fā) DOM 上的某個(gè)事件時(shí),會(huì)產(chǎn)生一個(gè)事件對(duì)象event
惑惶,這個(gè)對(duì)象中包含著所有與事件有關(guān)的信息煮盼。包括導(dǎo)致事件的元素、事件的類型以及其他與特定事件相關(guān)的信息带污。例如僵控,鼠標(biāo)操作導(dǎo)致的事件對(duì)象中,會(huì)包含鼠標(biāo)位置的信息鱼冀,而鍵盤操作導(dǎo)致的事件對(duì)象中报破,會(huì)包含與按下的鍵有關(guān)的信息。所有瀏覽器都支持event
對(duì)象千绪,但支持方式不同充易。
13.3.1 DOM 中的事件對(duì)象
兼容 DOM 的瀏覽器會(huì)將一個(gè)event
對(duì)象傳入到事件處理程序中。
var btn = document.getElementById("myBtn");
btn.onclick = function (event) {
alert(event.type); //"click"
};
btn.addEventListener('click', function(event) {
alert(event.type); //"click"
}, false);
在事件處理程序內(nèi)部荸型,對(duì)象this
始終等于currentTarget
的值盹靴,而target
則只包含事件的實(shí)際目標(biāo)。如果直接將事件處理程序指定給了目標(biāo)元素瑞妇,則this
稿静、currentTarget
和target
包含相同的值。
var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert(event.currentTarget === this); //true
alert(event.target === this); //true
};
如果事件處理程序存在于按鈕的父節(jié)點(diǎn)中踪宠,那么這些值是不相同的自赔。
document.body.onclick = function(event) {
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById('myBtn')); //true
}
在需要通過一個(gè)函數(shù)處理多個(gè)事件時(shí),可以使用type
屬性柳琢。
var btn = document.getElementById('myBtn');
var handler = function(event) {
switch(event.type) {
case "click":
alert('clicked');
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
要阻止特定事件的默認(rèn)行為绍妨,可以使用preventDefault()
方法润脸。例如:鏈接的默認(rèn)行為就是在被單機(jī)時(shí)會(huì)導(dǎo)航到其href
特性指定的 URL。若想要阻止鏈接導(dǎo)航這一默認(rèn)行為他去,那么通過鏈接的onclick
事件處理程序可以取消它毙驯。
var link = document.getElementById('myLink');
link.onclick = function(event) {
event.preventDefault();
};
只有cancelable
屬性設(shè)置為true
的事件,才可以使用preventDefault()
來(lái)取消其默認(rèn)行為灾测。
另外爆价,stopPropagation()
方法用于立即停止事件在 DOM 層次中的傳播,即取消進(jìn)一步的事件捕捉或冒泡媳搪。例如:
var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert('Clicked');
event.stopPropagation();
};
document.body.onclick = function(event) {
alert('body clicked');
};
事件對(duì)象的eventPhase
屬性铭段,可以用來(lái)確定事件當(dāng)前正位于事件流的那個(gè)階段。如果是在捕獲階段調(diào)用的事件處理程序秦爆,那么等于1
序愚;如果事件處理程序處于目標(biāo)對(duì)象上,則等于2
等限;如果是在冒泡階段調(diào)用的事件處理程序爸吮,則等于3
。盡管“處于目標(biāo)”發(fā)生在冒泡階段望门,但eventPhase
仍然一直等于2
形娇。
var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener('click', function(event) {
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event) {
alert(event.eventPhase); //3
};
只有在事件處理程序執(zhí)行期間,
event
對(duì)象才會(huì)存在筹误,一旦事件處理程序執(zhí)行完成桐早,event
對(duì)象就會(huì)被銷毀。
13.3.2 IE 中的事件對(duì)象
要訪問 IE 中的event
對(duì)象有幾種不同的方式纫事,取決于指定事件處理程序的方法勘畔。在使用 DOM0 級(jí)方法添加事件處理程序時(shí)所灸,event
對(duì)象作為window
對(duì)象的一個(gè)屬性存在丽惶。
var btn = document.getElementById("myBtn");
btn.onclick = function () {
var event = window.event;
alert(event.type); //"click"
};
13.3.3 跨瀏覽器的事件對(duì)象
IE 中event
對(duì)象的全部信息和方法 DOM 對(duì)象中都有,只不過實(shí)現(xiàn)方式不一樣爬立。
var EventUtil = {
addHandler: function(element, type, handler) {
//省略的代碼
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
//省略的代碼
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
};
13.4 事件類型
“DOM3級(jí)事件”規(guī)定了一下幾類事件钾唬。
- UI 事件,當(dāng)用戶與頁(yè)面上的元素交互時(shí)觸發(fā)侠驯;
- 焦點(diǎn)事件抡秆,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā);
- 鼠標(biāo)事件吟策,當(dāng)用戶通過鼠標(biāo)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā)儒士;
- 滾輪事件,當(dāng)使用鼠標(biāo)滾輪(或類似設(shè)備)時(shí)觸發(fā)檩坚;
- 文本事件着撩,當(dāng)在文檔中輸入文本時(shí)觸發(fā)诅福;
- 鍵盤事件,當(dāng)用戶通過鍵盤在頁(yè)面上執(zhí)行操作時(shí)觸發(fā)拖叙;
- 合成事件氓润,當(dāng)為 IME(Input Method Editor)輸入字符時(shí)觸發(fā);
- 變動(dòng)事件薯鳍,當(dāng)?shù)讓?DOM 結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)咖气。
- 變動(dòng)名稱事件,當(dāng)元素或?qū)傩悦儎?dòng)時(shí)觸發(fā)挖滤。此類事件已經(jīng)被廢棄崩溪,沒有任何瀏覽器實(shí)現(xiàn)它們,因此本章不做介紹斩松。
13.4.1 UI 事件
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
- load 事件
當(dāng)頁(yè)面完全加載后(包括所有圖像悯舟、JavaScript 文件、CSS 文件等外部資源)砸民,就會(huì)觸發(fā)window
上面的load
事件抵怎。
圖像上面也可以觸發(fā)load
事件,無(wú)論是在 DOM 中的圖像元素還是 HTML 中的圖像元素岭参。 - unload 事件
這個(gè)事件在文檔被完全卸載后觸發(fā)反惕。只要用戶從一個(gè)頁(yè)面切換到另一個(gè)頁(yè)面,就會(huì)發(fā)生unload
事件演侯。而利用這個(gè)事件最多的情況是清除引用姿染,以避免內(nèi)存泄漏。 - resize 事件
當(dāng)瀏覽器窗口被調(diào)整到一個(gè)新的高度或?qū)挾葧r(shí)秒际,就會(huì)觸發(fā)resize
事件悬赏。這個(gè)事件在window
上面觸發(fā),因此可以通過 JavaScript 或者<body>
元素中的onresize
特性來(lái)指定事件處理程序娄徊。 - scroll 事件
雖然scroll
事件是在window
對(duì)象上發(fā)生的闽颇,但它實(shí)際表示的則是頁(yè)面中相應(yīng)元素的變化。
13.4.2 焦點(diǎn)事件
焦點(diǎn)事件會(huì)在頁(yè)面獲得或失去焦點(diǎn)時(shí)觸發(fā)寄锐。利用這些事件并與document.hasFocus()
方法及document.activeElement
屬性配合兵多,可以知曉用戶在頁(yè)面上的行蹤。
13.4.3 鼠標(biāo)與滾輪事件
13.4.4 鍵盤與文本事件
1.鍵碼
2.字符編碼
3.DOM3 級(jí)變化
4.textInput
事件
5.設(shè)備中的鍵盤事件
13.4.5 復(fù)合事件
13.4.6 變動(dòng)事件
13.4.7 HTML5 事件
- contextmenu 事件
- beforeunload 事件
- DOMContentLoaded 事件
- readystatechange 事件
- pageshow 和 pagehide 事件
- hashchange 事件
13.4.8 設(shè)備事件
13.4.9 觸摸與手勢(shì)事件
13.5 內(nèi)存和性能
13.5.1 事件委托
對(duì)“事件處理程序過多”問題的解決方案就是事件委托橄仆。事件委托利用了事件冒泡剩膘。
13.5.2 移除事件處理程序
btn.onclick = function() {
btn.onclick = null;
document.getElementById("myDiv").innerHTML = "Processing...";
}
13.6 模擬事件
13.7 小結(jié)
在使用事件時(shí),需要考慮如下一些內(nèi)存與性能方面的問題盆顾。
- 有必要限制一個(gè)頁(yè)面中事件處理程序的數(shù)量怠褐,數(shù)量太多會(huì)導(dǎo)致占用大量?jī)?nèi)存,而且也會(huì)讓用戶感覺頁(yè)面反應(yīng)不夠靈敏您宪。
- 建立在事件冒泡機(jī)制之上的事件委托技術(shù)奈懒,可以有效地減少事件處理程序的數(shù)量具温。
- 建議在瀏覽器卸載頁(yè)面之前移除頁(yè)面中的所有事件處理程序。
可以使用 JavaScript 在瀏覽器中模擬事件筐赔。