天天看點

JavaScript 事件流

文章目錄

  • ​​事件​​
  • ​​事件綁定​​
  • ​​事件流​​
  • ​​DOM标準事件流​​
  • ​​阻止事件冒泡/捕獲​​
  • ​​阻止預設事件​​
  • ​​事件代理(委托)​​

事件

JavaScript 使我們有能力建立動态頁面。事件是可以被 JavaScript 偵測到的行為。

網頁中的每個元素都可以産生某些可以觸發 JavaScript 函數的事件。

比方說,我們可以在使用者點選某按鈕時産生一個 onClick 事件來觸發某個函數。事件在 HTML 頁面中定義。

事件舉例:

  1. 滑鼠點選
  2. 頁面或圖像載入
  3. 滑鼠懸浮于頁面的某個熱點之上
  4. 在表單中選取輸入框
  5. 确認表單
  6. 鍵盤按鍵

事件綁定

事件綁定的三種方式:

<!-- 1. html标簽事件綁定 -->
<button onclick="show()" id="btn">Show</button>

<script>const btn = document.getElementById("btn");

    // 2. js事件綁定
    btn.onclick = show;

    // 3. 事件監聽
    btn.addEventListener("click", show);

    function show(event) {
        console.log(event.target);  // 2 3方式有event傳回
        event.preventDefault();  // 阻止預設行為
        console.log('show');
    }</script>      

事件流

事件流描述的是從頁面中接收事件的順序。

事件發生時會在元素節點與根節點之間按照特定的順序傳播,路徑所經過的所有節點都會收到該事件,這個傳播過程即DOM事件流。

事件傳播的順序對應浏覽器的兩種事件流模型:

  1. 冒泡型事件流:從 DOM 樹的葉子到根。(預設)
  2. 捕獲型事件流:從 DOM 樹的根到葉子。
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <div id="div">Click me!</div>
    <script>.getElementById("div").addEventListener("click", (event) => {
            console.log('this is div');
        });

        document.body.addEventListener("click", (event) => {
            console.log('this is body');
        });

        document.documentElement.addEventListener("click", (event) => {
            console.log('this is html');
        });

        document.addEventListener("click", (event) => {
            console.log('this is document');
        });

        // 預設是事件捕獲,是以按順序輸出:
        // this is div
        // this is body
        // this is html
        // this is document</script>
</body>
</html>      

上面這段html代碼中,單擊了頁面中的​

​<div>​

​元素,

在冒泡型事件流中click事件傳播順序為 ​

​<div> => <body> => <html> => document​

​ (預設)

在捕獲型事件流中click事件傳播順序為 ​

​document => <html> => <body> => <div>​

JavaScript 事件流
JavaScript 事件流

note:

  1. 所有現代浏覽器都支援事件冒泡,但在具體實作中略有差别:

    IE5.5及更早版本中事件冒泡會跳過元素(從body直接跳到document)。

    IE9、Firefox、Chrome、和Safari則将事件一直冒泡到window對象。

  2. IE9、Firefox、Chrome、Opera、和Safari都支援事件捕獲。盡管DOM标準要求事件應該從document對象開始傳播,但這些浏覽器都是從window對象開始捕獲事件的。
  3. 由于老版本浏覽器不支援,很少有人使用事件捕獲。建議使用事件冒泡。

DOM标準事件流

DOM标準采用捕獲+冒泡。兩種事件流都會觸發DOM的所有對象,從document對象開始,也在document對象結束。
JavaScript 事件流

DOM标準規定事件流包括三個階段:

  1. 事件捕獲階段:實際目标​

    ​<div>​

    ​​在捕獲階段不會接收事件。也就是在捕獲階段,事件從​

    ​document​

    ​​到​

    ​<html>​

    ​​再到​

    ​<body>​

    ​就停止了。
  2. 處于目标階段:事件在​

    ​<div>​

    ​上發生并處理。但是事件處理會被看成是冒泡階段的一部分。
  3. 事件冒泡階段:事件又傳播回文檔。

綁定事件時通過addEventListener函數,它有三個參數,第三個參數若是true,則表示采用事件捕獲,若是false(預設),則表示采用事件冒泡。

<div id="box1">box1
   <div id="box2">box2
       <div id="box3">box3</div>
   </div>
</div>
<script>.addEventListener('click', function () {
       console.log('box1 捕獲階段');
   }, true);
   box2.addEventListener('click', function () {
       console.log('box2 捕獲階段');
   }, true);
   box3.addEventListener('click', function () {
       console.log('box3 捕獲階段');
   }, true);
   box1.addEventListener('click', function () {
       console.log('box1 冒泡階段');
   }, false);
   box2.addEventListener('click', function () {
       console.log('box2 冒泡階段');
   }, false);
   box3.addEventListener('click', function () {
       console.log('box3 冒泡階段');
   }, false);</script>      
JavaScript 事件流

​element.addEventListener(event, function, useCapture)​

第三個參數​

​useCapture​

​,可選。布爾值,指定事件是否在捕獲或冒泡階段執行:

  • ​true​

    ​ - 事件句柄在捕獲階段執行
  • ​false​

    ​(預設)- 事件句柄在冒泡階段執行

阻止事件冒泡/捕獲

使用 ​

​event.stopPropagation()​

​ 起到阻止捕獲和冒泡階段中目前事件的進一步傳播。

  • W3C的方法是:​

    ​event.stopPropagation()​

  • IE則是使用:​

    ​event.cancelBubble = true​

p.addEventListener("click", (event) => {
    event.stopPropagation(); // 阻止事件冒泡
    console.log('this is p');  // 隻會輸出 'this is p'
});


document.addEventListener("click", (event) => {
    event.stopPropagation(); // 阻止事件捕獲
    console.log('this is document');  // 隻會輸出 'this is document'
}, true);      

相容IE的寫法:

window.event? window.event.cancelBubble = true : event.stopPropagation();      

阻止預設事件

  • W3C的方法是:​

    ​event.preventDefault()​

  • IE則是使用:​

    ​event.returnValue = false​

既然是說預設行為,當然是元素必須有預設行為才能被取消,如果元素本身就沒有預設行為,調用當然就無效了。

<a href="https://www.baidu.com/" id="a">阻止預設跳轉</a>

<script>.getElementById("a").addEventListener("click", (event) => {
        event.preventDefault();
        console.log('已阻止a連結跳轉');
    });</script>      

事件代理(委托)

事件代理的原理用到的就是事件冒泡和目标元素,把事件處理器添加到父元素,等待子元素事件冒泡,并且父元素能夠通過target(IE為srcElement)判斷是哪個子元素,進而做相應處理。
<body>
  <ul id="color-list">
    <li>red</li>
    <li>orange</li>
    <li>yellow</li>
    <li>green</li>
    <li>blue</li>
    <li>indigo</li>
    <li>purple</li>
  </ul>
  <script>// 不使用事件代理
    (function(){
        var colorList = document.getElementById("color-list");
        var colors = colorList.getElementsByTagName("li");
        for (var i = 0; i < colors.length; i++) {
            colors[i].addEventListener('click', showColor);
        };
    
        function showColor(e) {
            e = e || window.event;
            var targetElement = e.target || e.srcElement;
            console.log(targetElement.innerHTML);
        }
    })();
    
        // 使用事件代理
    (function(){  
          var colorList = document.getElementById("color-list");
          colorList.addEventListener('click', showColor);
  
          function showColor(e) {
              e = e || window.event;
              var targetElement = e.target || e.srcElement;
              console.log(targetElement.innerHTML);
          }
    })();</script>
</body>      

事件代理的好處:

  1. 将多個事件處理器減少到一個,因為事件處理器要駐留記憶體,這樣就提高了性能。想象如果有一個100行的表格,對比傳統的為每個單元格綁定事件處理器的方式和事件代理(即table上添加一個事件處理器),不難得出結論,事件代理确實避免了一些潛在的風險,提高了性能。
  2. DOM更新無需重新綁定事件處理器,因為事件代理對不同子元素可采用不同處理方法。如果新增其他子元素(a,span,div等),直接修改事件代理的事件處理函數即可,不需要重新綁定處理器,不需要再次循環周遊。
<li><span>li中的span的内容</span></li>

<script>$(document).on('click', 'li', function(e){
        alert('li');
    });

    $(document).on('click', 'span', function(e){
        alert('span');
    })</script>      
$(document).on('click', 'li', function(e){
    alert('li');
});

$(document).on('click', 'span', function (e) {
    alert('span');
    e.stopPropagation();  // 阻止了事件冒泡,但不會阻擊預設行為(如超連結的跳轉)
    // return false;  // 阻止了事件冒泡,也阻止了預設行為
})      

繼續閱讀