DOM事件流與事件委託

事件流與事件委託

事件,即文檔或瀏覽器中發生的一些特定交互的瞬間,咱們能夠利用事件監聽來預約事件,當事件發生的時候執行相應的處理程序。當事件發生在某個DOM節點上時,事件在DOM結構中進行一級一級的傳遞,這便造成了「流」,事件流便描述了從頁面中接收事件的順序。本文主要討論事件流的三個階段,及利用事件委託機制進行性能優化。html

DOM事件流

關於事件流的理解,《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

上述過程示意圖:
DOM事件流

阻止事件冒泡

咱們可使用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>");

簡單說,事件委託就是把原本該本身接收的事件委託給本身的上級(父級,祖父級等等)的某個節點,讓本身的「長輩們」幫忙盯着,一旦有事件觸發,再由「長輩們」告訴本身:「喂,孫子,有人找你~~」。
恩,差很少就是這麼個意思,可憐天下父母心。

水平有限,歡迎你們不吝指正。

相關文章
相關標籤/搜索