DOM 的事件操作(監(jiān)聽和觸發(fā)),都定義在EventTarget接口挖函。所有節(jié)點對象都部署了這個接口磷仰,其他一些需要事件通信的瀏覽器內置對象,比如XMLHttpRequest颁井,也部署了這個接口厅贪。
該接口主要提供三個實例方法。
- addEventListener: 綁定事件
- removeEventListener: 移除事件
- dispatchEvent: 觸發(fā)事件
以addEventListener
為例
btn.addEventListener('click',function(){console.log(this)},false)
第三個參數默認為false雅宾,可省略养涮,指該事件只在冒泡階段觸發(fā);若為true,則為在捕獲階段眉抬。
addEventListener方法可以為針對當前對象的同一個事件贯吓,添加多個不同的監(jiān)聽函數。這些函數按照添加順序觸發(fā)蜀变,即先添加先觸發(fā)悄谐。
用隊列理解,先進先出库北。
- 進:addEventListener
- 出:removeEventListener
如果為同一個事件多次添加同一個監(jiān)聽函數爬舰,該函數只會執(zhí)行一次,多余的添加將自動被去除(不必使用removeEventListener方法手動去除)寒瓦。
注意情屹,removeEventListener方法移除的監(jiān)聽函數,必須是addEventListener方法添加的那個監(jiān)聽函數杂腰,而且必須在同一個元素節(jié)點屁商,否則無效。
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
上面代碼中,removeEventListener方法無效蜡镶,因為監(jiān)聽函數不是同一個匿名函數。
監(jiān)聽函數
瀏覽器的事件模型恤筛,就是通過監(jiān)聽函數對事件做出反應官还。事件發(fā)生后,瀏覽器聽到了這個事件毒坛,就會執(zhí)行相應的監(jiān)聽函數望伦。這就是事件驅動編程模式的主要編程方式。
JavaScript 有三種方法煎殷,可以為事件綁定監(jiān)聽函數屯伞。
1. addEventListener (推薦這種)
所有 DOM 節(jié)點實例都有addEventListener方法,用來為該節(jié)點定義事件的監(jiān)聽函數豪直。
window.addEventListener('load', doSomething, false);
相比另外兩種有如下優(yōu)點:
- 同一個事件可以添加多個監(jiān)聽函數劣摇。
- 能夠指定在哪個階段(捕獲階段還是冒泡階段)觸發(fā)監(jiān)聽函數。
- 除了 DOM 節(jié)點弓乙,其他對象(比如window末融、XMLHttpRequest等)也有這個接口,它等于是整個 JavaScript 統(tǒng)一的監(jiān)聽函數接口暇韧。
2. 元素節(jié)點的事件屬性
元素節(jié)點對象的事件屬性勾习,同樣可以指定監(jiān)聽函數。
window.onload = doSomething;
div.onclick = function (event) {
console.log('觸發(fā)事件');
};
使用這個方法指定的監(jiān)聽函數懈玻,也是只會在冒泡階段觸發(fā)巧婶。
注意,這種方法與 HTML 的on-屬性的差異是涂乌,它的值是函數名doSomething
艺栈,而不像后者,必須給出完整的監(jiān)聽代碼doSomething()
骂倘。
同一個事件只能定義一個監(jiān)聽函數眼滤,也就是說,如果定義兩次onclick屬性历涝,后一次定義會覆蓋前一次诅需。因此,也不推薦使用荧库。
3. HTML 的 on- 屬性(不推薦)
<body onload="doSomething()">
<div onclick="console.log('觸發(fā)事件')">
上面代碼為body節(jié)點的load事件堰塌、div節(jié)點的click事件,指定了監(jiān)聽代碼分衫。一旦事件發(fā)生场刑,就會執(zhí)行這段代碼。
注意蚪战,這些屬性的值是將會執(zhí)行的代碼牵现,而不是一個函數铐懊。
一旦指定的事件發(fā)生,on-屬性的值是原樣傳入 JavaScript 引擎執(zhí)行瞎疼。因此如果要執(zhí)行函數科乎,不要忘記加上一對圓括號。
對象this贼急、currentTarget和target
在事件處理程序內部茅茂,對象this始終等于currentTarget的值,而target則只包含事件的實際目標太抓。如果直接將事件處理程序指定給了目標元素空闲,則this、currentTarget和target包含相同的值走敌。
1 var btn = document.getElementById("myBtn");
2 btn.onclick = function (event) {
3 alert(event.currentTarget === this); //ture
4 alert(event.target === this); //ture
5 };
這個例子檢測了currentTarget和target與this的值碴倾。由于click事件的目標是按鈕,一次這三個值是相等的悔常。如果事件處理程序存在于按鈕的父節(jié)點中影斑,那么這些值是不相同的。
1 document.body.onclick = function (event) {
2 alert(event.currentTarget === document.body); //ture
3 alert(this === document.body); //ture
4 alert(event.target === document.getElementById("myBtn")); //ture
5 };
當單擊這個例子中的按鈕時机打,this和currentTarget都等于document.body矫户,因為事件處理程序是注冊到這個元素的。然而残邀,target元素卻等于按鈕元素皆辽,以為它是click事件真正的目標。由于按鈕上并沒有注冊事件處理程序芥挣,結果click事件就冒泡到了document.body驱闷,在那里事件才得到了處理。
currentTarget始終是監(jiān)聽事件者空免,而target是事件的真正發(fā)出者空另。
注意,在jQuery提供的on方法中蹋砚,e.currentTarget與該方法接收的第二個參數有關扼菠,根據jQuery的文檔描述
如果省略selector或者是null,那么事件處理程序被稱為直接事件 或者 直接綁定事件 坝咐。每次選中的元素觸發(fā)事件時循榆,就會執(zhí)行處理程序,不管它直接綁定在元素上墨坚,還是從后代(內部)元素冒泡到該元素的
當提供selector參數時秧饮,事件處理程序是指為委派事件(事件委托或事件代理)。事件不會在直接綁定的元素上觸發(fā),但當selector參數選擇器匹配到后代(內部元素)的時候盗尸,事件處理函數才會被觸發(fā)柑船。jQuery 會從 event target 開始向上層元素(例如,由最內層元素到最外層元素)開始冒泡振劳,并且在傳播路徑上所有綁定了相同事件的元素若滿足匹配的選擇器椎组,那么這些元素上的事件也會被觸發(fā)。
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style>
li{
padding: 5px;
border: 1px solid red;
}
span{
border: 1px solid #000;
}
</style>
</head>
<body>
<ul>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
</ul>
<script>
let ul = document.querySelectorAll('ul')[0]
let aLi = document.querySelectorAll('li')
$('ul').on('click','li',function(e){
console.log(e.target) // 被點擊的元素
console.log(e.currentTarget) // li
console.log(e.currentTarget === this) // true
})
</script>
</body>
</html>
當li中含有子元素的時候历恐,e.target指的是觸發(fā)事件的元素,可能是span也可能是li专筷,此時的e.currentTarget指的是selector那個參數弱贼,也就是本例中的li。如果省略selector參數磷蛹,那么它和addEventListener中e.target和e.currentTarget是一致的吮旅。
事件委托
事件委托就是利用事件冒泡機制,指定一個事件處理程序味咳,來管理某一類型的所有事件庇勃。
舉個取快遞的例子:
公司的員工們經常會收到快遞。為了方便簽收快遞槽驶,有兩種辦法:一種是快遞到了之后收件人各自去拿快遞责嚷;另一種是委托前臺MM代為簽收,前臺MM收到快遞后會按照要求進行簽收掂铐。
很顯然罕拂,第二種方案更為方便高效,同時這種方案還有一種優(yōu)勢全陨,那就是即使有新員工入職爆班,前臺的MM都可以代替新員工簽收快遞。
這個例子之所以非常恰當形象辱姨,是因為這個例子包含了委托的兩層意思:首先柿菩,現(xiàn)在公司里的員工可以委托前臺MM代為簽收快遞,即程序中現(xiàn)有的dom節(jié)點是有事件的并可以進行事件委托雨涛;其次枢舶,新入職的新員工也可以讓前臺MM代為簽收快遞,即程序中新添加的dom節(jié)點也是有事件的镜悉,并且也能委托處理事件祟辟。
如果一個ul中有100li,每個li都需要處理click事件侣肄,我們可以遍歷所有l(wèi)i旧困,給它們添加事件處理程序,這并不是理想的解決方案。
事件委托怎么實現(xiàn)呢吼具?因為冒泡機制僚纷,既然點擊子元素時,也會觸發(fā)父元素的點擊事件拗盒。那么我們就可以把點擊子元素的事件要做的事情怖竭,交給最外層的父元素來做,讓事件冒泡到最外層的dom節(jié)點上觸發(fā)事件處理程序陡蝇,這就是事件委托痊臭。