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,讓它來完成相應邏輯,自己不實現相應邏輯,這個模式,就是所謂的事件委託。