天天看點

事件的冒泡與捕獲

事件

摘自W3school

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

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

事件流

事件流描述的是從頁面中接收事件的順序。不過IE(微軟)提出的是冒泡流;網景提出的是捕獲流。

事件冒泡

IE提出的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點,自下而上去觸發事件,可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。所有現代的浏覽器都支援事件冒泡。下面為一個冒泡執行個體:

<div id="parent">
    parent
    <div id="child">child</div>
</div>

<script>
    var p = document.getElementById('parent');
    var c = document.getElementById('child');
    // 先彈出c後是p
    c.onclick = function(e){alert('c')};
    p.onclick = function(){alert('p')};
</script>
           

事件捕獲

網景公司提出的事件流叫做事件捕獲,和事件冒泡相反,即事件會從最外層開始發生,直到最具體的元素,自上而下去觸發事件。但是老版本的浏覽器是不支援的,是以預設還是冒泡。下面為一個捕獲執行個體:

<div id="parent">
    parent
    <div id="child">child</div>
</div>

<script>
    var p = document.getElementById('parent');
    var c = document.getElementById('child');
    // 先彈出p後是c
    p.addEventListener('click', function(){alert("p")}, true);
    c.addEventListener('click', function(){alert("c")}), true;
</script>
           

DOM事件流模型

又分為DOM0/DOM2/DOM3級事件。

DOM2級事件規定的事件流包含3個階段,事件捕獲階段、處于目标階段、事件冒泡階段。首先發生的事件捕獲為截獲事件提供機會,然後是實際的目标接收事件,最後一個階段是事件冒泡階段,可以在這個階段對事件做出響應。事件模型,如下圖:

事件的冒泡與捕獲

由上圖可知:

1. 一個完整的JS事件流是從window開始,最後回到window的一個過程

2. 事件流被分為三個階段(1~5)捕獲過程、(5~6)目标過程、(6~10)冒泡過程

示例:

<div id="parent">
    parent
    <div id="child">child</div>
</div>

<script>
    var p = document.getElementById('parent'),
        c = document.getElementById('child');

    /*
     點選子節點依次的觸發順序為:window捕獲,父節點捕獲,子節點捕獲,子節點冒泡,父節點冒泡,window冒泡
     點選父節點依次的觸發順序為:window捕獲,父節點捕獲,父節點冒泡,window冒泡
     target/currentTarget/eventPhase 都是event裡面的屬性。
     target是真正發生事件的DOM元素,currentTarget是目前事件發生在哪個DOM元素上,eventPhase表示目前所處階段。
     eventPhase值為1/2/3,分别表示為:捕獲階段/目标階段/冒泡階段
     目标階段指target === currentTarget或者eventPhase === 2的時候。
     window沒有nodeName,是以為undefined
     */
    c.addEventListener('click', function (e) {
        console.log('子節點捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    }, true);
    c.addEventListener('click', function (e) {
        console.log('子節點冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    }, false);
    p.addEventListener('click', function (e) {
        console.log('父節點捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    }, true);
    p.addEventListener('click', function (e) {
        console.log('父節點冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    }, false);
    window.addEventListener('click', function (e) {
        console.log('window捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    }, true);
    window.addEventListener('click', function (e) {
        console.log('window冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
    });
</script>
           

以上執行結果如下圖:

事件的冒泡與捕獲

阻止冒泡

e.stopPropagation()或者e.stopImmediateProgation()或者設定e.cancelBubble = true;均可阻止事件冒泡。

e.cancelBubble = true

适用老牌IE浏覽器,不過現在主流浏覽器也支援,效果同e.stopPropagation();

e.stopPropagation()

可阻止事件的向上繼續傳播,但不會阻止該元素綁定的其他事件的執行;

e.stopImmediateProgation()

做了兩件事,先是阻止綁定在該元素上的其他事件,再阻止其向上傳播。

<div id="parent">
    parent
    <div id="child">child</div>
</div>

<script>
    var p = document.getElementById('parent');
    var c = document.getElementById('child');
    // 第一種情況:先c後p,預設冒泡
    c.onclick = function(e){alert('c')};
    p.onclick = function(){alert('p')};

    // /*
    // 第二種情況:先p後c
    // 原理分析:addEventListener(event, function, boolean)屬于DOM2級事件,但有相容性問題;
    // 對于方法的相容性檢視,可去can i use 即caniuse.com檢視。
    // 第三個參數預設不填(即為false)表示冒泡,填寫true則表示捕獲;
    // 它允許給同一個事件注冊多個監聽器,下面第五種情況會涉及到。
    // */
    // p.addEventListener('click', function(){alert("p")}, true);
    // c.addEventListener('click', function(){alert("c")}), true;

    // // 第三種情況:隻有c,三種方法都可阻止冒泡
    // // c.onclick = function(e){alert('c'); e.cancelBubble = true};
    // // c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
    // c.onclick = function(e){alert('c'); e.stopPropagation();};
    // p.onclick = function(){alert('p')};

    // /*
    // 第四種情況:先c後p
    // 原因分析:onclick屬于DOM0事件,如果同一個元素綁定多個事件,會被後者覆寫
    // */
    // // c.onclick = function(e){alert('c'); e.cancelBubble = true};
    // // c.onclick = function(e){alert('c'); e.stopPropagation();};
    // c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
    // c.onclick = function(e){alert('c')};
    // p.onclick = function(){alert('p')};

    // // 第五種情況:先c後ccc,綁定的後續事件不能被阻止
    // // c.addEventListener('click', function(e){alert("c"); e.cancelBubble = true;});
    // c.addEventListener('click', function(e){alert("c"); e.stopPropagation();});
    // c.addEventListener('click', function(e){alert("ccc")});
    // p.addEventListener('click', function(){alert("p")});

    // // 第六種情況:隻有c,綁定的後續事件被阻止
    // c.addEventListener('click', function(e){alert("c"); e.stopImmediatePropagation();});
    // c.addEventListener('click', function(e){alert("ccc")});
    // p.addEventListener('click', function(){alert("p")});
</script>
           

阻止預設行為

所謂的預設行為就比如是:點選a标簽的時候就跳轉;點選表單的送出按鈕的時候就會送出表單等。有時候不希望這些事發生,是以就要阻止預設行為了。

1. 使用w3c的

e.preventDefault()

方法,在IE中使用

e.returnValue = false

。可阻止預設行為,但不會阻止冒泡,要想阻止冒泡再添加相應的阻止冒泡邏輯;

2. 在原生js中也可使用

return false

阻止預設行為,但是該方法在 用addEventListener/attachEvent綁定事件的時候,不生效,必須使用方法1;

3. 在jQuery中使用

return false

即可阻止預設行為又可阻止冒泡。

實作邏輯:

<div id='parent'>
    <a href="http://www.baidu.com" id="a">child</a>     
</div>

<script>
    var p = document.getElementById('parent');
    var a = document.getElementById('a');

    // 正常情況:先彈a,後p,最後跳轉
    a.onclick = function (e) {
        alert('彈框關閉後,頁面會跳轉');
    };

    // // 方法1
    // a.onclick = function (e) {
    //  alert('彈框關閉後,也不會跳轉');
    //  if(e.preventDefault){
    //      e.preventDefault();
    //  }else{
    //      window.event.returnValue == false;
    //  }
    //  // e.stopPropagation()
    // };

    // // 方法2 不會跳轉
    // a.onclick = function (e) {
    //  alert('彈框關閉後,也不會跳轉');
    //  e.stopPropagation();
    //  return false;
    // };
    // // 方法2 仍會跳轉
    // a.addEventListener('click', function (e) {
    //  alert('彈框關閉後,也不會跳轉了');
    //  // e.preventDefault();
    //  return false;
    // });

    // // 方法3 
    // $('a').on('click', function () {
    //  alert('彈框關閉後,也不會跳轉了');
    //  return false;
    // });
    p.onclick = function(){alert('p')};
</script>