使用原生JS實(shí)現(xiàn)事件委托

為什么要用事件委托?

首先,需要了解一下常用的事件監(jiān)聽方法有哪些區(qū)別:

常用的監(jiān)聽方法的區(qū)別

通常,在頁面中監(jiān)聽事件的方式有以下3種:

HTML屬性 (DOM Level 0)

HTML允許在元素標(biāo)簽的屬性中直接定義以下事件的監(jiān)聽代碼,比如下面例子監(jiān)聽了<button> </button>標(biāo)簽的click 事件:

<button onclick="alert('Hello World')">click me</button>

DOM元素屬性(DOM Level 0)

Element節(jié)點(diǎn)對象事件屬性指定監(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é)點(diǎn)、document節(jié)點(diǎn)、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)在使用中只能對當(dāng)前對象的一個事件指定一個監(jiān)聽函數(shù),這里就產(chǎn)生了幾個問題:

  1. 無法對同一個對象的同種事件注冊多個事件監(jiān)聽函數(shù)
  2. 當(dāng)我們需要對多個對象注冊同一個事件監(jiān)聽函數(shù)時,需要為每個對象準(zhǔn)備一套代碼,代碼會出現(xiàn)冗余
  3. 當(dāng)用JS創(chuàng)建元素時,新的元素?zé)o法被監(jiān)聽到

因此,W3C在DOM Level2 中為我們提供了新的事件模型,其特點(diǎn)是:

  1. DOM事件模型不依賴于特定的事件處理屬性
  1. 可以對任意對象的任何一種事件注冊多個監(jiān)聽函數(shù)

新的事件模型解決了DOM Level 0 中的幾個問題,因此,推薦使用addEventListener()這個方法。


下面我們來看一下關(guān)于事件委托的例子:

首先,需要一個簡單的頁面:

<div class="box">
  <ul>
    <li>選項(xiàng)<span>另一段話</span></li>
    <li>選項(xiàng)<span>另一段話</span></li>
    <li>選項(xiàng)<span>另一段話</span></li>
    <li>選項(xiàng)<span>另一段話</span></li>
  </ul>
</div>

在這個頁面中,以對象節(jié)點(diǎn).box 作為委托對象,為其綁定一個監(jiān)聽函數(shù),并獲取我們在觸發(fā)click事件時所點(diǎn)擊節(jié)點(diǎn)究竟是哪個:

var  div = document.querySelector('.box')
div.addEventListener('click', function(e){
  var tar = e.target
  console.log(str)
})

接下來,假設(shè)需要監(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 默認(rèn)返回大寫  
      console.log('找到了li')
    }
})

此時,<li>元素可以在觸發(fā)click事件時正常做出反應(yīng),但是這里有個問題,在例子中,<li>元素中還有<span>,如果將<span>當(dāng)作<li>的一部分,上面的代碼在點(diǎn)擊<span>元素時是無法觸發(fā)回調(diào)的,接下來繼續(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 默認(rèn)返回大寫  
      console.log('找到了爸爸是li')
    }
})

通過尋找父節(jié)點(diǎn)是否符合我們希望監(jiān)聽的對象,但是如果不是父節(jié)點(diǎn),而是祖先的某個節(jié)點(diǎn)呢?

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é)點(diǎn)一直尋找不到<li>,就會尋找至文檔根節(jié)點(diǎn),最后返回一個null,會使得while循環(huán)報錯,既然使用了while循環(huán),那么在尋找父節(jié)點(diǎn)找到委托節(jié)點(diǎn)<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事件委托。

當(dāng)然這里還有其他學(xué)習(xí)的地方:

  1. 如果希望監(jiān)聽的對象有部分子元素不觸發(fā)事件回調(diào),以上的方法則需要修改。
  2. 如何正確的封裝事件委托。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容