一個DOM元素綁定多個事件時,先執行冒泡還是捕獲


綁定在被點擊元素的事件是按照代碼順序發生,其他元素通過冒泡或者捕獲“感知”的事件,按照W3C的標准,先發生捕獲事件,后發生冒泡事件。所有事件的順序是:其他元素捕獲階段事件 -> 本元素代碼順序事件 -> 其他元素冒泡階段事件 。

一個DOM元素綁定兩個事件,一個冒泡,一個捕獲,則事件會執行多少次,執行順序如何。

首先來了解一下冒泡和捕獲是怎么回事:

1.冒泡

冒泡是從下向上,DOM元素綁定的事件被觸發時,此時該元素為目標元素,目標元素執行后,它的的祖元素綁定的事件會向上順序執行。如下代碼所示,四個嵌套的div:

addEventListener函數的第三個參數設置為false說明不為捕獲事件,即為冒泡事件。

<div id='one'>
  <div id='two'>
    <div id='three'>
      <div id='four'>
      </div>
    </div>
  </div>
</div>

<script type='text/javascript'>
  var one=document.getElementById('one');
  var two=document.getElementById('two');
  var three=document.getElementById('three');
  var four=document.getElementById('four');
  one.addEventListener('click',function(){
    alert('one');
  },false);
  two.addEventListener('click',function(){
    alert('two');
  },false);
  three.addEventListener('click',function(){
    alert('three');
  },false);
  four.addEventListener('click',function(){
    alert('four');
  },false);
</script>

代碼的執行順序是:

點擊one元素,輸出one;

點擊two元素,輸出two one;

點擊three元素,輸出 three two one;

點擊four元素,輸出 four three two one;

2.捕獲

捕獲則和冒泡相反,目標元素被觸發后,會從目標元素的最頂層的祖先元素事件往下執行到目標元素為止。

將上面的代碼第三個參數均改為true,則執行結果如下:

點擊one,輸出one;

點擊two,輸出one two;

點擊three,輸出one two three;

點擊four,輸出one two three four;

很明顯執行順序是不同的。

 

3.當一個元素綁定兩個事件,一個冒泡,一個捕獲

首先,無論是冒泡事件還是捕獲事件,元素都會先執行捕獲階段。

從上往下,如有捕獲事件,則執行;一直向下到目標元素后,從目標元素開始向上執行冒泡元素,即第三個參數為true表示捕獲階段調用事件處理程序,如果是false則是冒泡階段調用事件處理程序。(在向上執行過程中,已經執行過的捕獲事件不再執行,只執行冒泡事件。)

 如下代碼:

one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},true);
four.addEventListener('click',function(){
alert('four');
},false);

此時點擊four元素,four元素為目標元素,one為根元素祖先,從one開始向下判斷執行。

one為捕獲事件,輸出one;

two為冒泡事件,忽略;

three為捕獲時間,輸出three;

four為目標元素,開始向上冒泡執行,輸出four;(從此處分為兩部分理解較容易。)

three為捕獲已執行,忽略;

two為冒泡事件,輸出two;

one為捕獲已執行,忽略。

最終執行結果為:one three four two

(在這里可能會有疑問,目標元素是什么事件有區別嗎?我的測試結果是沒有區別的,無論目標元素是捕獲還是冒泡,在w3c下都是先從根元素執行捕獲到目標元素,再從目標元素向上執行。)

例如,three作為目標元素,執行結果為:one three two(因為two是冒泡事件,在向下執行時沒有執行到)。

執行次數:綁定了幾個事件便執行幾次。

如下代碼,two元素綁定了兩個不同事件,點擊two都會執行這兩個事件。而執行順序有所差異

one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two,bubble');
},false);
two.addEventListener('click',function(){
alert('two,capture');
},true);
three.addEventListener('click',function(){
alert('three,bubble');
},true);
four.addEventListener('click',function(){
alert('four');
},true);

 

1、如果two為目標元素,目標元素的事情按順序執行而其他元素根據W3C的標准執行,即先捕獲后冒泡

點擊two執行結果:one(因為是two的父元素支持捕獲事件所以先執行)  two,bubble  two,capture(順序執行,注意逗號不是間隔,是輸出內容。)

2、如果目標元素不是two,則two的兩個事件按先捕獲后冒泡觸發執行,也就是跟前面討論的執行過程是一樣的,只不過兩個事件都綁定在同一個DOM元素上。

點擊three執行結果:one two,capture three,bubble two,bubble

再來一個例子:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title></title>
  <style type="text/css">
    #p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; }
    #c { width: 200px; height: 200px; border: 1px solid red; }
    #sub { width: 100px; height: 100px; border: 1px solid red; }
  </style>
</head>
<body>
  <div id="p">
    parent
    <div id="c">
      child
    </div>
  </div>
  <script type="text/javascript">
    window.alert = function (msg) {
      console.log(msg);
    };
    var p = document.getElementById('p'),
        c = document.getElementById('c');
    p.addEventListener('click', function (e) {
      alert('父節點冒泡')
    }, false);

    c.addEventListener('click', function (e) {
      alert('子節點捕獲')
    }, true);
    c.addEventListener('click', function (e) {
      alert('子節點冒泡')
    }, false);
    p.addEventListener('click', function (e) {
      alert('父節點捕獲')
    }, true);
  </script>
</body>
</html>

點擊子div:

p.addEventListener('click', function (e) {
  alert('父節點冒泡')
}, false);

c.addEventListener('click', function (e) {
  alert('子節點冒泡')
}, false);
c.addEventListener('click', function (e) {
  alert('子節點捕獲')
}, true);

p.addEventListener('click', function (e) {
  alert('父節點捕獲')
}, true);

 

所有事件的順序是:其他元素捕獲階段事件 -> 本元素代碼順序事件 -> 其他元素冒泡階段事件 。

還是那句話,沒在實際應用中踩過坑,可能你真的不算掌握了這個知識點。那么,事件捕獲有什么常見的坑呢?

一、事件捕獲

1.坑一:新插入的子元素沒有綁定點擊事件

可能不少同學在初學js的時候遇到過這樣一個坑。一個Ul元素中出事的狀態有4個li元素,我們可以循環給li元素添加click事件,執行我們想要的動作。這個例子的坑就在於,新添加的li元素不會有我們綁定的click事件。

<ul class="container">

    <!-- 先循環給原有的4個li綁定click事件 -->
    <li class="item"></li>
    <li class="item"></li>
    <li class="item"></li>
    <li class="item"></li>
    
    <!-- 這是新添加的li元素,但是該元素沒綁定click事件 -->
    <li class="item new"></li>
</ul>

是的,就是這么坑,為了解決這個問題,我們就要利用事件捕獲的原理。 

$('ul.container').click(function(event) {
    var target = event.target;
    if (target.className == 'item') {
        // dosomething
    }
})

在上面的解決方案中,我並不是直接給li綁定事件,而是給所有li的父級ul綁定事件。根據事件捕獲的原理,事件會自上而下傳遞給li,我們只需要通過一些簡單的條件判斷來確定我們的目標元素即可,如上例中的判斷li的className。

這個方式就是大名鼎鼎的事件委托。(可以看我的另一篇博客js中的事件委托

事件委托是一個很重要而且在實際中會常常用到的知識點。

2. 坑二: 如果目標元素有子元素,那么怎么辦?

當我們在使用className判斷目標元素時,會遇到這樣的情況。

...
<li class="item">
    <div class="title">xxx</title>
    <p class="desc">xxxxxxs</p>
</li>
...

當我們試圖使用事件委托希望給所有的li添加元素時,在利用className判斷的過程中發現,目標event.target元素居然是li.item的子元素,這個時候就沒辦法准確的將事件綁定到li上了,這個時候我們應該怎么辦?

這種情況,我們要做的,就是阻止事件捕獲的傳遞,那么,要如何阻止呢?

我知道的一種方式,利用css,給li所有子元素添加如下css屬性即可

li.item >  * {
  pointer-events: none;
}

按道理來說,應該是有js方式的,可是查了很多文章都沒有提及,因此就暫時這樣吧,到時候遇到了在補充。

在jquery中,已經幫助我們實現了事件委托,並且幫我們解決掉了這些坑。我們只需要按照一定語法使用即可,而我們不用再自己去進行條件判斷,比如我們要給所有的li.item元素綁定事件,寫法如下

// on中的第二參數就是我們的目標元素的選擇器
$('ul.container').on('click', 'li.item', function(event) {
    // dosomething
})

二、事件冒泡

說完了事件捕獲,然后來說說事件冒泡這個坑。所謂事件冒泡,就是讓DOM樹最底層的目標元素最先接收到事件,然后往上傳遞,這是一個自下而上的過程。

1.彈窗關閉時阻止冒泡

我們常常會遇到一種彈窗樣式,彈窗出來時,內容在中間,然后會有一層半透明的遮罩將頁面內容與彈窗區分開。彈窗內容會有一些按鈕綁定點擊事件,比如確認與取消。而在半透明遮罩上,可能也會綁定一個點擊事件,當點擊時,將彈窗隱藏。如果我由於經驗不足,將該遮罩層設置成了彈窗按鈕的父級,那么就會遇到事件冒泡帶來的麻煩。

也就是說,在如下例子中 container是全屏遮罩,button是彈窗里面的點擊按鈕。他們都同時綁定了click事件,執行不同的動作。但是在實際執行的時候,當我點擊了button,那么button和container的click事件都會執行,自下而上按順序執行

<div className="container">
    <div className="button">click</div>
</div>

好吧解決問題的方法很簡單,就是阻止冒泡事件。

$xxx.click(function(e) {
    e.stopPropagation();
    
    // ie
    e.cancelBubble = true;
})

 

2.使用toggle時阻止冒泡

 $(function(){  
         //toggle div  
         $("#gga").click(function(event){  
             event.stopPropagation(); //阻止冒泡事件  
             $(".toggleoption").slideToggle("fast");  
         });  
   
         //slideup div  
         $(document).click(function(event)  {   
             var eo  =  $(event.target);   

             if($(".toggleoption").is(":visible") && eo.attr("class") != "toggleoption" && !eo.parent(".toggleoption").length) {  
                 $('.toggleoption').slideUp("fast");   
             }  
         });   
     }) 

理解了整個事件流,我們可以感受事件在DOM中的傳遞過程與方向,並且利用他來解決我們的問題和各種坑,雖然是一個簡單的知識點,但是卻非常容易被忽略,因此建議大家找機會將它掌握牢固。

而還有一個坑,可能大家在實際中會很少遇到,這個坑就是,某些事件類型天生就不支持事件冒泡!

blur: 在元素失去焦點時觸發,該事件不支持冒泡
focus: 在元素獲得焦點時觸發,該事件不支持冒泡
mouseenter: 當鼠標移入元素時觸發,該事件不支持冒泡
mouseleave: 當鼠標移出元素時觸發,該事件不支持冒泡
... ...

以及IE6 IE7 IE8中 的 change、select、submit、reset 事件事實上都沒有參照規范定義產生事件冒泡。

當你在需要的冒泡的時候,綁定了這些事件,而你恰好不知道居然還有事件天生就不支持冒泡的,那么你可能就悲劇了。所以這個點只要在腦袋里面有個印象就好了,我只記得我曾經遇到過這樣一個坑,但暫時想不起來應用場景了 - -!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM