理解:javascript事件捕獲 與 事件冒泡

術語定義
1.事件流描述的是從頁面中接收事件的順序,也可理解爲事件在頁面中傳播的順序。
2.事件就是用戶或瀏覽器自身執行的某種動作。諸如click(點擊)、load(加載)、mouseover(鼠標懸停)。
3.事件處理程序響應某個事件的函數就叫事件處理程序(或事件偵聽器)。


事件流描述的是從頁面中接受事件的順序,但有意思的是,

IE和Netscape開發團隊居然提出了兩個截然相反的事件流概念:

  • IE的事件流是事件冒泡流;

  • 而Netscape的事件流是事件捕獲流;

IE的事件流是冒泡, 從裏面往上面冒, netscape是從外部元素往內部元素捕獲;


DOM2級的事件規定了事件流包含三個階段包括: 1:事件捕獲, 2:處於目標階段, 3:事件冒泡階段(IE8以及更早版本不支持DOM事件流); 無論在DOM0還是DOM2還是DOM3中都會在事件函數中傳入事件對象;

wKiom1nc_6uhqOEGAABLxFxphK0445.png-wh_50

  • 捕獲階段(event  capturing):在事件冒泡的模型中,捕獲階段不會響應任何事件;

    通俗的理解就是,當鼠標點擊或者觸發dom事件時,瀏覽器會從根節點開始由外到內進行事件傳播,即點擊了子元素,如果父元素通過事件捕獲方式註冊了對應的事件的話,會先觸發父元素綁定的事件。

  • 目標階段:目標階段就是指事件響應到觸發事件的最底層元素上;

  • 冒泡階段(dubbed  bubbling):冒泡階段就是事件的觸發響應會從最底層目標一層層地向外到最外層(根節點),事件代理即是利用事件冒泡的機制把裏層所需要響應的事件綁定到外層;


無論是事件捕獲還是事件冒泡,它們都有一個共同的行爲,就是事件傳播,它就像一跟引線,只有通過引線才能將綁在引線上的鞭炮(事件監聽器)引爆,試想一下,如果引線不導火了,那鞭炮就只有一響了!

wKioL1ndfR_QCy-3AADI46SiLnY963.png-wh_50

DOM2級事件'定義了兩個方法,

用於處理指定和刪除事件處理程序的操作:addEventListener()removeEventListener()


說到事件冒泡與捕獲就不得不提一下兩個用於事件綁定的方法addEventListenerattachEvent。當然還有其它的事件綁定的方式這裏不做介紹。

  addEventListener(event, listener, useCapture)  

參數定義:


    • event---(事件名稱,如click,不帶on),

    • listener---事件監聽函數,

    • useCapture---是否採用事件捕獲進行事件捕捉(默認爲false,即採用事件冒泡方式)

   addEventListener在 IE11、Chrome 、Firefox、Safari等瀏覽器都得到支持。

  attachEvent(event,listener)  

參數定義:


    • event---(事件名稱,如onclick,帶on)

    • listener---事件監聽函數。

   attachEvent主要用於IE瀏覽器,並且僅在IE10及以下才支持,IE11已經廢了這個方法了(微軟還是挺識趣的,慢慢向標準靠攏)。

下面就用上面這兩個方法通過子來解釋一下事件捕獲與事件冒泡的具體表現行爲差異,

事件冒泡 

例1:

<html lang="zh-cn">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>js事件機制</title>
    <style>
        #parent{
            width: 200px;
            height:200px;
            text-align: center;
            line-height: 3;
            background: green;
        }
        #child{
            width: 100px;
            height: 100px;
            margin: 0 auto;
            background: orange;
        }
    </style>
    </head>
<body>
    <div id="parent">
        父元素
        <div id="child">
            子元素
        </div>
    </div>
    <script type="text/javascript">
        var parent = document.getElementById("parent");
        var child = document.getElementById("child");
    
        document.body.addEventListener("click",function(e){
            console.log("click-body");
        },false);
        
        parent.addEventListener("click",function(e){
            console.log("click-parent");
        },false);

        child.addEventListener("click",function(e){
            console.log("click-child");
        },false);
    </script>
</body>
</html>

通過"addEventListener"方法,採用事件冒泡方式給dom元素註冊click事件,點擊子元素會發生什麼呢?

如果你對事件冒泡有一定了解的話那你肯定知道上面的代碼會輸出的順序,沒錯,如下圖所示:

wKiom1nd7zKx5-fTAAAYz4k_GKs265.png-wh_50


事件觸發順序是由內到外的,這就是事件冒泡,雖然只點擊子元素,但是它的父元素也會觸發相應的事件,其實這是合理的,因爲子元素在父元素裏面,點擊子元素也就相當於變相的點擊了父元素,這樣理解對吧?

這裏有同學可能要問了,如果點擊子元素不想觸發父元素的事件怎麼辦?

肯定可以的,那就是停止事件傳播---event.stopPropagation();

修改例1的代碼,在子元素的監聽函數中加入停止事件傳播的操作,2:

child.addEventListener("click",function(e){
  console.log("click-child");
   e.stopPropagation();
},false);

在點擊子元素的時候就只彈出了子元素那條信息,父元素的事件沒有觸發,因爲事件已經停止傳播了,冒泡階段也就停止了。

事件冒泡差不多就講述完了,別急,捕獲還沒說呢!

事件捕獲

例3,修改子1中的代碼,給parent元素註冊一個捕獲事件,如下:

var parent = document.getElementById("parent");
        var child = document.getElementById("child");
    
        document.body.addEventListener("click",function(e){
            console.log("click-body");
        },false);
        
        parent.addEventListener("click",function(e){
            console.log("click-parent---事件傳播");
        },false);
     
     //新增事件捕獲事件代碼
        parent.addEventListener("click",function(e){
            console.log("click-parent--事件捕獲");
        },true);

        child.addEventListener("click",function(e){
            console.log("click-child");
        },false);

如果你看明白了我前面說的那些,你就知道這個栗子的輸出順序了:

wKioL1ndgcnRaifiAAAiWPWnBWw701.png-wh_50

父元素通過事件捕獲的方式註冊了click事件,所以在事件捕獲階段就會觸發,然後到了目標階段,即事件源,之後進行事件傳播,parent同時也用冒泡方式註冊了click事件,所以這裏會觸發冒泡事件,最後到根節點。這就是整個事件流程。


敲黑板了啊:《javascript 事件取消和阻止冒泡

---取消默認操作

w3c 的方法是 e.preventDefault(),IE 則是使用 e.returnValue = false;

在支持 addEventListener() 的瀏覽器中,也能通過調用時間對象的 preventDefault() 方法取消時間的默認操作。不過,在 IE9 之前的 IE 中,可以通過設置事件對象的 returnValue 屬性爲 false 來達到同樣的效果。下面的代碼假設一個事件處理程序,它使用全部的三種取消技術:

function cancelHandler(event){  
  var event = event || window.event;  //用於IE  
  if(event.preventDefault) event.preventDefault();  //標準技術  
  if(event.returnValue) event.returnValue = false;  //IE  
  return false;   //用於處理使用對象屬性註冊的處理程序  
}

當前的 DOM 事件模型草案定義了 Event 對象屬性 defaultPrevented。

return false

javascript 的 return false 只會阻止默認行爲,而是用 jQuery 的話則既阻止默認行爲又防止對象冒泡。

下面這個使用原生 JS,只會阻止默認行爲,不會停止冒泡

<div id='div'  onclick='alert("div");'>  
  <ul  onclick='alert("ul");'>  
    <li id='ul-a' onclick='alert("li");'>
    <a href="<a href="http://caibaojian.com/" id="testB" >caibaojian.com<="" a><="" li"="" target="_blank">http://caibaojian.com/"id="testB">caibaojian.com</a></li</a>>  
  </ul>  
</div>  

var a = document.getElementById("testB"); 
 
a.onclick = function(){ 
   return false;  
};

---阻止冒泡

w3c 的方法是 e.stopPropagation(),IE 則是使用 e.cancelBubble = true

在支持 addEventListener() 的瀏覽器中,可以調用事件對象的一個 stopPropagation() 方法已阻止事件的繼續傳播。如果在同一對象上定義了其他處理程序,剩下的處理程序將依舊被調用,但調用 stopPropagation() 方法可以在事件傳播期間的任何時間調用,它能工作在捕獲階段、事件目標本身中和冒泡階段。

IE9 之前的IE不支持 stopPropagation() 方法。相反,IE事件對象有一個 cancleBubble 屬性,設置這個屬性爲 true 能阻止事件進一步傳播。( IE8 及之前版本不支持事件傳播的捕獲階段,所以冒泡是唯一待取消的事件傳播。)

當前的 DOM 事件規範草案在 Event 對象上定義了另一個方法,命名爲stopImmediatePropagation()。類似 stopPropagation(),這個方法組織了任何其他對象的事件傳播,但也阻止了在相同對象上註冊的任何其他事件處理程序的調用。

<div id='div' onclick='alert("div");'>  
  <ul onclick='alert("ul");'>  
    <li onclick='alert("li");'>test</li>  
  </ul>  
</div>

阻止冒泡

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


注意:

關於DOM0、DOM2、DOM3級事件模型請參考我的另一篇博客:

理解:javascript中DOM0,DOM2,DOM3級事件模型


參考文章:

北方吹雪的博客http://www.cnblogs.com/bfgis/p/5460191.html

開源中國2015的博客:http://wiki.jikexueyuan.com/project/brief-talk-js/event-cancellation-and-prevent-bubbles.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章