通過JAVAScript事件的冒泡來動態為元素綁定事件的方法稱為事件委托(Event Delegation,也稱為“事件代理”),是 JavaScript 中最熱門的技術之一,在筆試和面試中是常考察的重點知識點,今天來簡單介紹一下相關的原理知識。
1、DOM事件流
1.1 事件流描述的是從頁面中接收事件的順序。
1.2 事件發生時會在元素節點之間按照特定的順序傳播,這個傳播過程即DOM事件流。
比如:我們給頁面中的一個div注冊了單擊事件,當你單擊了div時,也就單擊了body,單擊了html,單擊了document。
1.3 DOM 事件流會經歷3個階段:
(1)、捕獲階段:事件從文檔的根節點流向目標對象。
(2)、當前目標階段:在目標對象上被觸發。
(3)、冒泡階段:回溯到文檔的根節點。
2、事件捕獲
事件從最不精確的對象(document 對象)開始觸發,然后到最精確(也可以在窗口級別捕獲事件,不過必須由開發人員特別指定),與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。同樣形象的比喻一下可以想象成警察逮捕屋子內的小偷,就要從外面一層層的進入到房子內。
3、事件冒泡
微軟提出了名為事件冒泡的事件流。事件按照從最特定的事件目標到最不特定的事件目標(document對象)的順序觸發。可以想象把一顆石頭投入水中,泡泡會一直從水底冒出水面。也就是說,事件會從最內層的元素開始發生,一直向上傳播,直到document對象。
防止事件冒泡的方法:
- event.stopPropagation(); // 阻止了事件冒泡,但不會阻擊默認行為。
- event.preventDefault(); // 阻止默認事件,比如a的跳轉事件。
4、事件委托
什么是事件委托?
事件委托也稱為事件代理。當有多個列表元素需要綁定事件時,一個一個去綁定即浪費時間,又不利于性能,這時候就可以用到事件委托,給他們的一個共同父級元素添加一個事件函數去處理他們所有的事件情況。
事件委托的原理
給父元素注冊事件,利用事件冒泡,當子元素的事件觸發,會冒泡到父元素,然后去控制相應的子元素。
案例
<ul>
<li>事件冒泡,事件委派</li>
<li>事件冒泡,事件委派</li>
<li>事件冒泡,事件委派</li>
<li>事件冒泡,事件委派</li>
<li>事件冒泡,事件委派</li>
</ul>
<script>
// 事件委托的核心原理:給父節點添加偵聽器, 利用事件冒泡影響每一個子節點
var ul = document.querySelector('ul');
ul.addEventListener('click', function (e) {
// e.target 這個可以得到我們點擊的對象
e.target.style.backgroundColor= 'aqua';
})
</script>
在上述代碼沒有給每個li標簽綁定事件,而是通過給ul標簽綁定事件,然后判斷target的形式(冒泡)來設置每個子節點相應的處理。
tips:addEventListener(type,listener[,useCapture])第三個參數如果是true,表示在事件捕獲階段調用事件處理程序,如果是false(默認為false),表示事件冒泡階段調用事件處理程序。
為什么要使用事件委托?
在 javascript 中,頁面中事件處理程序的數量與頁面整體性能直接相關。
每個函數都是對象,都占用內存空間,對象越多,性能越差。
為指定事件處理程序所需訪問 DOM 的次數會造成整個頁面交互的延遲。
“過多事件處理程序“的解決方案是使用事件委托。
事件委托是利用事件冒泡,可以只使用一個事件處理程序來管理一種類型的事件。
只要給所有元素共同的祖先節點添加一個事件處理程序,就可以解決大片雷同的只為指定事件處理程序的代碼的問題。
事件委托的作用
- 減少內存消耗和DOM操作,提高性能。
- 節省花在設置頁面事件處理程序上的時間。指定一個事件處理程序可以節省 DOM 引用,也可以節省時間。
事件委托的注意事項
使用“事件委托”時,并不是說把事件委托給的元素越靠近頂層就越好。事件冒泡的過程也需要耗時,越靠近頂層,事件的“事件傳播鏈”越長,也就越耗時。如果DOM嵌套結構很深,事件冒泡通過大量祖先元素會導致性能損失。
不支持冒泡的事件
冒泡事件有很多,常見的不支持冒泡的事件如下:
- 焦點事件:focus、blur。
- 鼠標事件: mouseenter、mouseleave。
- UI事件:load、unload、scroll、resize。
原因是在于:這些事件僅發生于自身上,而它的任何父節點上的事件都不會產生,所以不會冒泡。
總結
事件委托是基于事件冒泡實現的動態綁定事件的方法。因為把事件綁定到父節點上,因此減少了綁定事件(減少內存消耗)和減少DOM讀取次數。就算后面新增的子節點也有了相關事件,刪除部分子節點不用去銷毀對應節點上綁定的事件。父節點是通過event.target來找對應的子節點的。