事件,即文檔或瀏覽器中發生的一些特定交互的瞬間,咱們能夠利用事件監聽來預約事件,當事件發生的時候執行相應的處理程序。當事件發生在某個DOM節點上時,事件在DOM結構中進行一級一級的傳遞,這便造成了「流」,事件流便描述了從頁面中接收事件的順序。本文主要討論事件流的三個階段,及利用事件委託機制進行性能優化。html
關於事件流的理解,《JS高程三》中有個形象的比喻:瀏覽器
能夠想象畫在一張紙上的一組同心圓,若是你把手指放在圓心上,那麼你的手指指向的其實不是一個圓,而是紙上全部的圓。...>換句話說,在單擊按鈕的同時,你也單擊了按鈕的容器元素,甚至也單擊了整個頁面。性能優化
————《JavaScript高級程序設計(第三版)》page 345app
DOM2級事件中規定事件流包含3個階段:函數
捕獲階段性能
處於目標階段優化
冒泡階段spa
首先發生的是事件捕獲階段,此時事件尚未傳遞到目標節點對象上,因此咱們就有機會在這個階段進行事件的截。而後是目標節點接收到事件,最後是事件冒泡階段,能夠在這個階段對事件作出處理和響應。
咱們先定義一段簡單的html結構:設計
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <div class="box"> <button type="button" name="button">click me</button> </div> </body> </html>
在事件捕獲階段中,先由不具體的節點(即上層節點)接收到事件,而後一級一級往下傳遞,直到最具體的目標節點
接收到事件。
在DOM2級事件規範中,要求事件從document
對象開始傳遞,可是諸如Chrome,Firefox等主流瀏覽器倒是從window
開始傳遞的。code
addEventListener
方法的第三個參數是一個布爾值(可選),指定事件處理程序是否在捕獲或冒泡階段執行。 當爲true
時,則事件處理程序將在捕獲階段執行。誤區:不管
addEventListener
的第三個參數是否爲true
,三個階段都會走一遍,這裏的第三個參數,指的是處理程序將會在捕獲或者冒泡階段執行,比如是你想買菜,你能夠在上班路上,或者下班路上完成買菜,但不管何時買菜,你都要把這兩段路程走完。
document.querySelector('#btn').addEventListener('click', function () { console.log("btn was clicked"); },true); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); },true); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); },true); document.addEventListener('click', function () { console.log("document was clicked"); },true); window.addEventListener('click', function () { console.log("window was clicked"); },true);
點擊click me
按鈕後,控制檯依次打印出執行結果:
window was clicked document was clicked body was clicked box was clikced btn was clicked
很明顯能夠看出,在捕獲階段,事件由window
對象開始,一級一級地向下傳遞,直到傳遞到最具體的button
對象上。
事件冒泡階段與捕獲階段剛好相反,冒泡階段是從最具體的目標對象開始,一層一層地向上傳遞,直到window
對象。addEventListener
方法默認就是從冒泡階段
執行事件處理程序。
document.querySelector('#btn').addEventListener('click', function () { console.log("btn was clicked"); }); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); }); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); }); document.addEventListener('click', function () { console.log("document was clicked"); }); window.addEventListener('click', function () { console.log("window was clicked"); });
點擊click me
按鈕後,控制檯依次打印出執行結果:
btn was clicked box was clikced body was clicked document was clicked window was clicked
上述過程示意圖:
咱們可使用event.stopPropagation()
方法阻止事件冒泡過程,以防止事件冒泡而帶來沒必要要的錯誤和困擾。
示例:
document.querySelector('#btn').addEventListener('click', function (event) { console.log("btn was clicked"); event.stopPropagation(); }); document.querySelector('body').addEventListener('click', function () { console.log("body was clicked"); }); document.querySelector('.box').addEventListener('click', function () { console.log("box was clicked"); }); document.addEventListener('click', function () { console.log("document was clicked"); }); window.addEventListener('click', function () { console.log("window was clicked"); });
點擊click me
按鈕後,控制檯打印出執行結果顯示,事件沒有再向上冒泡傳遞給其餘節點對象:
btn was clicked
每一個函數都是對象,都會佔用內存,因此當咱們的頁面中所包含的事件數量較多時,若是給每一個節點綁定一個事件,加上事件處理程序,就會形成性能不好。還有一個問題是,某個元素節點是後來經過JavaScript動態添加進頁面中的,這時候咱們若是提早對它進行綁定,但此時該元素並不存在,因此會綁定事件會失敗。解決上述兩個問題的一個經常使用方案,就是使用事件委託
。
舉例來講:
document.querySelector('.box').addEventListener(function (event) { switch (event.target.id) { case "btn": console.log("btn was clicked"); break; case "btn-2": console.log("btn-2 was clicked"); break; default: console.log("box was clicked"); break; } }); $(".box").append("<button id='btn-2'>btn-2</button>");
簡單說,事件委託就是把原本該本身接收的事件委託給本身的上級(父級,祖父級等等)的某個節點,讓本身的「長輩們」幫忙盯着,一旦有事件觸發,再由「長輩們」告訴本身:「喂,孫子,有人找你~~」。
恩,差很少就是這麼個意思,可憐天下父母心。
水平有限,歡迎你們不吝指正。