為什么要用事件委托?
首先斩萌,需要了解一下常用的事件監(jiān)聽方法有哪些區(qū)別:
常用的監(jiān)聽方法的區(qū)別
通常匿垄,在頁面中監(jiān)聽事件的方式有以下3種:
HTML屬性 (DOM Level 0)
HTML允許在元素標簽的屬性中直接定義以下事件的監(jiān)聽代碼,比如下面例子監(jiān)聽了<button> </button>
標簽的click 事件:
<button onclick="alert('Hello World')">click me</button>
DOM元素屬性(DOM Level 0)
對Element
節(jié)點對象事件屬性指定監(jiān)聽函數(shù):
<button id=aButton>click me</button>
<script>
var button = document.querySelector('#aButton')
button.onclick = function(e){
alert('Hello world');
}
</script>
addEventListener方法(DOM Level 2)
通過Element
節(jié)點、document
節(jié)點茧痕、window
對象的addEventListener
方法,定義事件監(jiān)聽函數(shù):
<button id=aButton>click me</button>
<script>
var button = document.querySelector('#aButton')
button.addEventListener('click', function(){
alert('Hello world')
})
</script>
DOM為我們提供了以上三種指定監(jiān)聽函數(shù)的方法恼除。
前兩種方法(DOM Level 0)在使用中只能對當前對象的一個事件指定一個監(jiān)聽函數(shù)踪旷,這里就產(chǎn)生了幾個問題:
- 無法對同一個對象的同種事件注冊多個事件監(jiān)聽函數(shù)
- 當我們需要對多個對象注冊同一個事件監(jiān)聽函數(shù)時,需要為每個對象準備一套代碼豁辉,代碼會出現(xiàn)冗余
- 當用JS創(chuàng)建元素時令野,新的元素無法被監(jiān)聽到
因此,W3C在DOM Level2 中為我們提供了新的事件模型徽级,其特點是:
- DOM事件模型不依賴于特定的事件處理屬性
- 可以對任意對象的任何一種事件注冊多個監(jiān)聽函數(shù)
新的事件模型解決了DOM Level 0 中的幾個問題气破,因此,推薦使用addEventListener()
這個方法餐抢。
下面我們來看一下關于事件委托的例子:
首先现使,需要一個簡單的頁面:
<div class="box">
<ul>
<li>選項<span>另一段話</span></li>
<li>選項<span>另一段話</span></li>
<li>選項<span>另一段話</span></li>
<li>選項<span>另一段話</span></li>
</ul>
</div>
在這個頁面中,以對象節(jié)點.box
作為委托對象旷痕,為其綁定一個監(jiān)聽函數(shù)碳锈,并獲取我們在觸發(fā)click
事件時所點擊節(jié)點究竟是哪個:
var div = document.querySelector('.box')
div.addEventListener('click', function(e){
var tar = e.target
console.log(str)
})
接下來,假設需要監(jiān)聽里面的<li>
觸發(fā)事件:
var div = document.querySelector('.box')
console.log(div)
div.addEventListener('click', function(e){
var tar = e.target
if(tar.tagName.toLowerCase() === 'li'){
// tagName 默認返回大寫
console.log('找到了li')
}
})
此時欺抗,<li>
元素可以在觸發(fā)click
事件時正常做出反應售碳,但是這里有個問題,在例子中,<li>
元素中還有<span>
贸人,如果將<span>
當作<li>
的一部分间景,上面的代碼在點擊<span>
元素時是無法觸發(fā)回調的,接下來繼續(xù)修改:
var div = document.querySelector('.box')
console.log(div)
div.addEventListener('click', function(e){
var tar = e.target
if(tar.tagName.toLowerCase() === 'li' ||
tar.parentNode.tagName.toLowerCase() === 'li'
){
// tagName 默認返回大寫
console.log('找到了爸爸是li')
}
})
通過尋找父節(jié)點是否符合我們希望監(jiān)聽的對象艺智,但是如果不是父節(jié)點倘要,而是祖先的某個節(jié)點呢?
var div = document.querySelector('.box')
console.log(div)
div.addEventListener('click', function(e){
var tar = e.target
while(tar.tagName.toLowerCase() !== 'li'){
tar = tar.parentNode
}
if(tar){
alert('找到了祖輩是li')
}else{
alert('沒找到呢')
}
})
這里仍然有問題力惯,假如我們觸發(fā)的事件的對象的祖先節(jié)點一直尋找不到<li>
碗誉,就會尋找至文檔根節(jié)點,最后返回一個null父晶,會使得while
循環(huán)報錯,既然使用了while
循環(huán)弄跌,那么在尋找父節(jié)點找到委托節(jié)點<div>
時跳出循環(huán)甲喝,這個問題就得到了解決:
var div = document.querySelector('.box')
console.log(div)
div.addEventListener('click', function(e){
var tar = e.target
while(tar.tagName.toLowerCase() !== 'li'){
if(tar===div){
tar = null
break
}
tar = tar.parentNode
}
if(tar){
alert('找到了祖輩是li')
}else{
alert('沒找到呢')
}
})
此時,無論我們觸發(fā)事件的元素是否監(jiān)聽對象的子元素铛只,或者觸發(fā)事件的元素是監(jiān)聽對象的祖輩埠胖,都有正確的處理路徑。
以上就是完整的原生JS事件委托淳玩。
當然這里還有其他學習的地方:
- 如果希望監(jiān)聽的對象有部分子元素不觸發(fā)事件回調直撤,以上的方法則需要修改。
- 如何正確的封裝事件委托蜕着。