JS 中的事件委托是什么
大家好,我是前端西瓜哥。今天我們來認識一下事件委托。
所謂事件委托,就是將原本應該在當前元素綁定的事件,放到它的祖先元素上,讓祖先元素來委托處理。
事件流
事件流指從頁面中接收事件的順序,也可理解為事件在頁面中傳播的順序。
事件流由兩階段組成:
- 捕獲事件
- 冒泡事件
我們通常用 addEventListener 給元素添加事件:
document.querySelector('#card')addEventListener( 'click', function (event) { console.log('div#card 冒泡點擊', event); }, false);
第一個參數是事件名,第二個參數是事件響應函數,可以拿到當前的事件對象。
第三個參數是可選的,表示監聽的是否為捕獲階段,false為冒泡階段,也是默認值,true 為捕獲階段。我們常用的是冒泡階段。
當我們點擊元素時,就會執行這個函數。
假設我們的 DOM 結構如下:
<html> <head> <title>前端西瓜哥</title> <meta charset="UTF-8" /> </head> <body> <div id="app"> <div id="box-1"> <div id="card">card</div> </div> <div id="box-2"></div> </div> </body></html>
現在我們點擊 card 文字時,DOM 就會產生事件流。
事件流首先會進入 捕獲階段,從根節點往目標元素(div#card)移動,依次經過為:
- window
- document(文檔根元素,在 HTML 中沒有顯式聲明)
- document.documentElement(<html>)
- document.body(<body>)
- ...
- 目標元素 div#card
和調用事件對象的 event.composedPath() 方法拿到的 事件路徑 類似。
window 看起來是個全局變量,但它也是可以綁定事件的,比如窗口大小改變的 resize 事件就只能綁定到 window 上,而不能綁定到 document 上。
然后再執行 冒泡階段,然后反著再經過一遍這些節點。
我們會根據事件流經過的順序,依次執行這些節點上綁定的對應事件函數。
事件委托
假如我有一個好友列表,我希望點擊 “聊天” 按鈕,拿到對應用戶 id,創建并進入到對應用戶的聊天會話中。
<ul> <li>前端西瓜哥<button>聊天</button></li> <li>fe_watermelon<button>聊天</button></li> <!-- ... --> <li>老王<button>聊天</button></li></ul>
最直接的方式是給所有的 button 元素都綁定各自的事件。
節點少的時候還好,如果節點多達上千上萬個,就需要聲明相當多的事件函數,比較消耗內存。而且 如果列表經常發生動態變更,也會導致大量事件監聽的移除和綁定。
在這種情況下,事件委托就大有可為了。
事件委托正是利用事件流的冒泡特性,將本來要綁定到多個元素的事件函數,委托到了其祖先元素上。
在上面這個例子中,我們可以將事件綁定到 ul 節點上,執行函數時,通過 event 對象拿到必要的信息,進行統一的操作。
document.querySelector('ul').addEventListener('click', (event) => { const target = event.target; const userId = target.getAttribute('data-user-id'); if (userId) { joinChat(userId); }});
通過 event.target 我們能獲得這次事件流的目標節點,然后從該節點對象中提取出需要的信息。
在這里我們需要拿到用戶 id,所以需要給 button 元素添加類似 data-user-id 的自定義屬性,像這樣子:
<ul> <li>前端西瓜哥<button data-user-id="5">聊天</button></li> <li>fe_watermelon<button data-user-id="99">聊天</button></li> <!-- ... --> <li>老王<button data-user-id="63">聊天</button></li></ul>
這樣,不管 li 有多少,更新多頻繁,我們只需要維護一個函數就夠了。
結尾
事件委托,其實就是將原本應該綁定在子元素的的大量類似的事件監聽函數,改為綁定到父元素或祖先元素上,委托祖先元素來處理。
不過在實際開發中,需要用到事件為委托的場景還是比較少,因為我們的列表通常不會太長。