1. 程式人生 > >JS 基礎篇(六):事件冒泡與捕獲

JS 基礎篇(六):事件冒泡與捕獲

目錄:

一、事件冒泡

微軟提出了名為事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。也就是說,事件會從最內層的元素開始發生,一直向上傳播,直到document物件。

<html>
    <body>
        <div id="outer">
            <p id="inner">Click me!</p>
        </div>
    </body>
</html>

在事件冒泡的概念下在p元素上發生click事件的順序應該是p -> div -> body -> html -> document。 即如果p元素上綁定了click的處理事件將先被觸發否則將冒泡到div,在div上如果綁定了click的處理事件將被觸發否則將冒泡到body,以此類推,判斷是否有click事件的處理函式,有就觸發,沒有就冒泡到上一級直到到達document級。

示例:

<div id="s1">s1
    <div id="s2">s2</div>
</div>
<script>
    s1.addEventListener("click",function(e){
        console.log("s1 冒泡事件");
    },false);
    s2.addEventListener("click",function(e){
        console.log("s2 冒泡事件");
    },false);
</script>

//點選s2:
//s2 冒泡事件
//s1 冒泡事件

//點選s1:
//s1 冒泡事件

二、事件捕獲

網景提出另一種事件流名為事件捕獲(event capturing)。與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。

<html>
    <body>
        <div id="outer">
            <p id="inner">Click me!</p>
        </div>
    </body>
</html>

在事件捕獲的概念下在p元素上發生click事件的順序應該是document -> html -> body -> div -> p

即如果document元素上綁定了click的處理事件將先被觸發否則將進入到html中,在html上如果綁定了click的處理事件將被觸發否則將進入到body中,以此類推,判斷是否有click事件的處理函式,有就觸發,沒有就進入到下一級直到到達到觸發元素那一級。

示例:

<div id="s1">s1
    <div id="s2">s2</div>
</div>
<script>
    s1.addEventListener("click",function(e){
        console.log("s1 捕獲事件");
    },true);
    s2.addEventListener("click",function(e){
        console.log("s2 捕獲事件");
    },true);
</script>

//點選s2:
//s1 捕獲事件
//s2 捕獲事件

//點選s1:
//s1 捕獲事件

三、addEventListener的第三個引數

在上面的案例中,我們對addEventListener的第三個引數進行了設定,下面將介紹下addEventListener函式第三個引數的含義。addEventListener的第三個引數就是為冒泡和捕獲準備的.

addEventListener有三個引數:

element.addEventListener(event, function, useCapture)
  • 第一個引數是需要繫結的事件
  • 第二個引數是觸發事件後要執行的函式
  • 第三個引數預設值是false,表示在事件冒泡階段呼叫事件處理函式;如果引數為true,則表示在事件捕獲階段呼叫處理函式。

四、事件冒泡與事件捕獲同時存在

當事件捕獲和事件冒泡一起存在的情況,事件又是如何觸發呢。

這裡記被點選的DOM節點為target節點:

1、document 往 target節點,捕獲前進,遇到註冊的捕獲事件立即觸發執行 2、到達target節點,觸發事件(對於target節點上,是先捕獲還是先冒泡則捕獲事件和冒泡事件的註冊順序,先註冊先執行) 3、target節點 往 document 方向,冒泡前進,遇到註冊的冒泡事件立即觸發

示例:

//對於非target節點則先執行捕獲在執行冒泡
//對於target節點則是先執行先註冊的事件,無論冒泡還是捕獲
<div id="s1">s1
    <div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){
        console.log("s1 冒泡事件");         
},false);
s2.addEventListener("click",function(e){
        console.log("s2 冒泡事件");
},false);
        
s1.addEventListener("click",function(e){
        console.log("s1 捕獲事件");
},true);
        
s2.addEventListener("click",function(e){
        console.log("s2 捕獲事件");
},true);
</script>

點選s2,執行結果如下: s1 捕獲事件 s2 冒泡事件 s2 捕獲事件 s1 冒泡事件

分析: 點選s2,click事件從document->html->body->s1->s2(捕獲前進) 這裡在s1上發現了捕獲註冊事件,則輸出"s1 捕獲事件" 到達s2,已經到達目的節點, s2上註冊了冒泡和捕獲事件,先註冊的冒泡後註冊的捕獲,則先執行冒泡,輸出"s2 冒泡事件" 再在s2上執行後註冊的事件,即捕獲事件,輸出"s2 捕獲事件" 下面進入冒泡階段,按照s2->s1->body->html->documen(冒泡前進) 在s1上發現了冒泡事件,則輸出"s1 冒泡事件"

五、如何阻止事件冒泡或者事件捕獲

以冒泡事件為例:

1、通過event.stopPropagation()終止事件傳播 對某一個節點而言,如果不想它現在處理的事件繼續往上冒泡的話,我們可以終止冒泡,即在相應的處理函式內,加入event.stopPropagation(),終止事件的廣播分發,這樣事件停留在本節點,不會再往外傳播了。

<div id="s1">s1
    <div id="s2">s2</div>
</div>

s1.addEventListener("click",function(e){
    console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){
    e.stopPropagation(); //阻止冒泡到上面元素,冒泡鏈在此處中斷
    console.log("s2 冒泡事件");
},false);

//原先點選s2:
//s2 冒泡事件
//s1 冒泡事件

//阻止冒泡後點擊s2:
//s2 冒泡事件

2、對觸發事件元素進行過濾

事件包含最初觸發事件的節點引用 和 繫結處理事件節點的引用,那如果節點只處理自己觸發的事件即可,不是自己產生的事件不處理。 event.target指向的是觸發事件的dom節點,而event.currrentTarget 指向的是繫結事件的dom節點,我們可以通過這兩個target 是否相等,讓繫結事件的dom節點只處理其自身觸發的事件而不是下級冒泡傳遞上來的。

s1.addEventListener("click",function(event){
    if(event.target === event.currentTarget){
        console.log("s1 冒泡事件");
    }
},false);
s2.addEventListener("click",function(event){
    if(event.target === event.currentTarget){
        console.log("s2 冒泡事件");
    }
},false);

我們還可以獲取target的節點名、id等屬性進行過濾設定處理事件。

<ul id="color-list">
    <li>red</li>
    <li>yellow</li>
    <li>blue</li>
    <li>green</li>
    <li>black</li>
    <li>white</li>
</ul>

var color_list = document.getElementById('color-list');
color_list.addEventListener('click',showColor,false);
function showColor(e){
    var x = e.target;
    if(x.nodeName.toLowerCase() === 'li'){
        console.log('The color is ' + x.innerHTML);
    }
}

元素收到事件後,判斷事件是否符合要求,然後做相應的處理,然後事件繼續冒泡往上傳遞; 通過以上方式,我們把本來每個元素都要有的處理函式,都交給了其祖父節點元素來完成了,也就是說,li將自己的響應邏輯委託給ul,讓它來完成相應邏輯,自己不實現相應邏輯,這個模式,就是所謂的事件委託。