DOM事件
在我們的日常的生活中姨蝴,無(wú)時(shí)無(wú)刻不在發(fā)生著各種類型的事件俊啼。比如說(shuō)體育賽事、娛樂(lè)新聞左医、戰(zhàn)爭(zhēng)甚至天氣變化等等都可以被稱為事件。這些事件有三個(gè)共同的特點(diǎn):監(jiān)聽同木、變化浮梢、通知。
轉(zhuǎn)到代碼上彤路,當(dāng)用戶使用一個(gè)程序時(shí)秕硝,代碼會(huì)監(jiān)聽用戶的行為,而用戶行為發(fā)生變化時(shí)洲尊,代碼會(huì)得到通知远豺,繼而做出反應(yīng),這就是發(fā)生在代碼上的事件坞嘀。而DOM事件就是在DOM節(jié)點(diǎn)上發(fā)生的事件躯护,通常我們會(huì)為DOM元素注冊(cè)事件處理函數(shù),來(lái)監(jiān)聽DOM事件丽涩。
DOM事件可以分為以下兩類:
- level 0事件
button.onclick = function(e){alert('Hello world!')}// or:
<button onclick="alert('Hello world!')">
level 0事件是在W3C認(rèn)證前的事件棺滞,可以通過(guò)如上兩種方式進(jìn)行監(jiān)聽裁蚁。這種事件的特點(diǎn)是:每個(gè)元素和事件只能設(shè)置一個(gè)事件處理函數(shù),先設(shè)置的會(huì)被后設(shè)置的覆蓋继准。
- level 2事件
button.addEventListener('click', function(e){alert('Hello world')});
level 2事件是經(jīng)過(guò)W3C認(rèn)證的事件枉证,它將事件的傳播方式分為了三個(gè)階段:捕獲階段(Capturing Phase)、目標(biāo)階段(Target Phase)和冒泡階段(Bubble Phase)移必。
下圖是W3C對(duì)DOM事件派發(fā)過(guò)程的圖解室谚。
level 2事件通過(guò)EventTarget.addEventListener() 方法將指定的監(jiān)聽器注冊(cè)到EventTarget上,當(dāng)該對(duì)象觸發(fā)指定的事件時(shí)崔泵,指定的回調(diào)函數(shù)就會(huì)被執(zhí)行舞萄。該方法允許給一個(gè)事件注冊(cè)多個(gè) listener。
事件委托
在了解什么是DOM事件以及給DOM事件綁定監(jiān)聽器的幾種方法后管削,我們來(lái)談?wù)勈录小?/p>
-
e.target和e.currentTarget
當(dāng)我們給目標(biāo)元素target綁定一個(gè)事件監(jiān)聽器target.addEventListener(event, function(e){})倒脓,并指定回調(diào)函數(shù)function(e),函數(shù)的參數(shù)e表示事件含思。此時(shí)崎弃,e.target與e.currentTarget分別表示直接觸發(fā)事件的元素與被監(jiān)聽的元素。
舉個(gè)栗子:
<html>
<body>
<ul style='list-style:none;max-width:200px;border:1px solid;'>
<li>點(diǎn)我試試</li>
<ul>
<script>
(document.querySelector('ul')).addEventListener('click' ,function(e){
console.log('e.target')
console.log(e.target)
console.log('e.currentTarget')
console.log(e.currentTarget)
})
</script>
</body>
</html>
這段代碼為一個(gè)ul
元素綁定了一個(gè)監(jiān)聽器含潘,當(dāng)ul
上發(fā)生點(diǎn)擊事件時(shí)饲做,分別輸出e.target和e.currentTarget的值。
當(dāng)點(diǎn)擊ul
中的li
元素時(shí)遏弱,該段代碼的在控制臺(tái)輸出的結(jié)果如下:
此時(shí)盆均,e.target為直接點(diǎn)擊的元素
li
,而e.currentTarget為被監(jiān)聽的元素ul
漱逸。由此我們可以得到一個(gè)啟發(fā)泪姨,觸發(fā)事件的元素與被監(jiān)聽的元素不一定是一個(gè)元素。于是就來(lái)到了本文的重點(diǎn)內(nèi)容——事件委托饰抒。
- 如何進(jìn)行事件委托
什么情況下會(huì)用到事件委托呢肮砾?舉兩個(gè)例子。
(1) 當(dāng)存在多個(gè)元素可以共用同一個(gè)監(jiān)聽器袋坑。
<html>
<body>
<ul>
<li>點(diǎn)<span>這里</span></li>
<li>點(diǎn)這里</li>
</ul>
<style>
ul, li{
list-style:none;
border:1px solid;
padding:10px;
background:#ddd
}
li{
text-align:center;
margin:10px;
background:#fff
}
</style>
</body>
</html>
上面的代碼中定義了如圖所示的一個(gè)ul
仗处,它包裹著兩個(gè)li
,第一個(gè)li
中還有一個(gè)子元素span
枣宫。如果我們希望點(diǎn)擊兩個(gè)li
均執(zhí)行同一條命令時(shí)婆誓,第一種方法是為每個(gè)li
都綁定一個(gè)監(jiān)聽器,但當(dāng)li
很多時(shí)也颤,這樣處理就過(guò)于繁瑣洋幻。
這時(shí)我們會(huì)想到可以直接監(jiān)聽ul
,為ul
綁定事件函數(shù)歇拆,那么只要li
存在于ul
的內(nèi)部鞋屈,點(diǎn)擊任意的一個(gè)li
都會(huì)執(zhí)行這條命令范咨。但同樣存在一個(gè)問(wèn)題,當(dāng)點(diǎn)擊li
的外部,也就是圖中的灰色區(qū)域時(shí),命令同樣會(huì)被執(zhí)行迹鹅。
那么如何僅僅在點(diǎn)擊li
的覆蓋區(qū)域的時(shí)候才執(zhí)行這段命令呢,可以用以下這段代碼替蛉。
var ul=document.querySelector('ul')
ul.addEventListener('click',function(e){
var el = e.target
//判斷當(dāng)前點(diǎn)擊的元素是否為li,如果不是拄氯,執(zhí)行以下的while循環(huán)
while(el.tagName !== 'LI'){
//如果點(diǎn)擊的元素為ul躲查,直接跳出循環(huán)
if(el === ul){
el = null
break;
}
//否則,將當(dāng)前元素父元素賦給el
el=el.parentNode
}
//如果最后el不為null译柏,則打出'ok'
if(el){
console.log('ok')
}
//否則镣煮,打出'你點(diǎn)擊的不是li'
else console.log('你點(diǎn)擊的不是li')
})
這段代碼實(shí)現(xiàn)了當(dāng)點(diǎn)擊的區(qū)域在li
范圍內(nèi)時(shí),不管點(diǎn)擊的是li
元素本身鄙麦,還是li
的子元素span
典唇,都會(huì)執(zhí)行console.log('ok')
,但當(dāng)點(diǎn)擊區(qū)域超出li
的范圍胯府,則執(zhí)行' console.log('你點(diǎn)擊的不是li')'介衔。這就成功實(shí)現(xiàn)了一個(gè)事件委托。
(2) 用事件委托實(shí)現(xiàn)動(dòng)態(tài)監(jiān)控
還有一種情況骂因,也會(huì)用到事件委托炎咖,那就是需要?jiǎng)討B(tài)監(jiān)控的時(shí)候。
看以下代碼:
<html>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<button id=addButton>+</button>
</body>
</html>
<style>
li{
border: 1px solid;
}
</style>
<script>
addButton.onclick = function(){
var li = document.createElement('li')
li.textContent = 'new'
document.querySelector('ul').appendChild(li)
}
document.querySelector('ul').onclick = function(e){
console.log(e.target)
}
</script>
</body>
</html>
這段代碼實(shí)現(xiàn)的效果如下:
當(dāng)點(diǎn)擊左下角的加號(hào)按鈕時(shí)寒波,會(huì)增加一個(gè)新的li乘盼,同時(shí)在點(diǎn)擊li時(shí),在控制臺(tái)輸出被點(diǎn)擊的li的內(nèi)容影所。這就是用事件委托實(shí)現(xiàn)動(dòng)態(tài)監(jiān)控蹦肴。
小結(jié)
本文介紹了兩種用原生JS實(shí)現(xiàn)事件委托的方法,在不同的情境與需求下猴娩,這些方法可以做不同程度的優(yōu)化,需要結(jié)合實(shí)例展開分析勺阐。