聊聊事件冒泡與事件捕獲
什麽是事件?
事件是文檔和瀏覽器窗口中發生的特定的交互瞬間。
什麽是事件流:
事件流描述的是從頁面中接受事件的順序( 說白了就是解決頁面中事件流發生順序的問題。),但有意思的是,微軟(IE)和網景(Netscape)開發團隊居然提出了兩個截然相反的事件流概念,IE的事件流是事件冒泡流(event bubbling),而Netscape的事件流是事件捕獲流(event capturing)。
讓我們先聊聊DOM0級事件與DOM2級事件
- DOM0
直接通過 onclick寫在html裏面的事件, 比如:
在標簽內寫onclick事件
<input onclick="alert(1)" />
在JS寫onlicke=function(){}函數
1 document.getElementById("myButton").onclick = function () { 2 alert(‘thanks‘); 3 }
- DOM2
主流瀏覽器DOM2級事件是通過以下兩個方法用於處理指定和刪除事件處理程序的操作:
- 添加事件 addEvenetListener ------ 可以為元素添加多個事件處理程序,觸發時會按照添加順序依次調用。
- 刪除事件 removeEventListener ------- 不能移除匿名添加的函數。
它們都有三個參數:
- 第一個參數是事件名(如click)。
- 第二個參數是事件處理程序函數。 可以為匿名函數,也可以為命名函數(但如果需要刪除事件,必須是命名函數)
- 第三個參數如果是true則表示在捕獲階段調用,為false表示在冒泡階段調用。
使用DOM 2級事件處理程序的主要好處是可以添加多個事件處理程序,事件處理會按照他們的順序觸發,通過addEventListener添加的事件只能用removeEventListener來移除,移除時傳入的參數與添加時使用的參數必須相同,這也意味著添加的匿名函數將無法移除,(註意:我們默認的第三個參數都是默認false,是指在冒泡階段添加,大多數情況下,都是將事件處理程序添加到事件的冒泡階段,這樣可以最大限度的兼容各個瀏覽器
匿名函數
1 //這是一個DOM 2級事件 添加事件最簡單的方式(此時添加的是一個匿名函數) 2 <button>按鈕</button> 3 <script> 4 var btn=document.querySelector(‘button‘); 5 btn.addEventListener(‘click‘,function(){ 6 console.log(‘我是按鈕‘) 7 },false) //當第三個參數不寫時,也是默認為false(冒泡時添加事件) 8 </script>
命名函數
1 <button>按鈕</button> 2 <script> 3 var btn=document.querySelector(‘button‘); 4 btn.addEventListener(‘click‘,foo,false); 5 function foo(){ 6 console.log(‘我是按鈕‘) 7 } 8 //其實操作就是把寫在裏面的函數拿到了外面,而在原來的位置用函數名來代替 9 </script>
看完以上的,我們再了解事件冒泡與捕獲
第一種(事件冒泡)IE提出
IE提出的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body onclick="bodyClick()"> 8 9 <div onclick="divClick()"> 10 <button onclick="btn()"> 11 <p onclick="p()">點擊冒泡</p> 12 </button> 13 </div> 14 <script> 15 16 function p(){ 17 console.log(‘p標簽被點擊‘) 18 } 19 function btn(){ 20 console.log("button被點擊") 21 } 22 function divClick(event){ 23 console.log(‘div被點擊‘); 24 } 25 function bodyClick(){ 26 console.log(‘body被點擊‘) 27 } 28 29 </script> 30 31 </body> 32 </html>
接下來我們點擊一下頁面上的p元素,如下所示
正如上面我們所說的,它會從一個最具體的的元素接收,然後逐級向上傳播, p=>button=>div=>body..........事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。
第二種(事件捕獲)網景提出
事件捕獲流的思想是不太具體的DOM節點應該更早接收到事件,而最具體的節點應該最後接收到事件,針對上面同樣的例子,點擊按鈕,那麽此時click事件會按照這樣傳播:(下面我們就借用addEventListener的第三個參數來模擬事件捕獲流)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 9 <div> 10 <button> 11 <p>點擊捕獲</p> 12 </button> 13 </div> 14 <script> 15 var oP=document.querySelector(‘p‘); 16 var oB=document.querySelector(‘button‘); 17 var oD=document.querySelector(‘div‘); 18 var oBody=document.querySelector(‘body‘); 19 20 oP.addEventListener(‘click‘,function(){ 21 console.log(‘p標簽被點擊‘) 22 },true); 23 24 oB.addEventListener(‘click‘,function(){ 25 console.log("button被點擊") 26 },true); 27 28 oD.addEventListener(‘click‘, function(){ 29 console.log(‘div被點擊‘) 30 },true); 31 32 oBody.addEventListener(‘click‘,function(){ 33 console.log(‘body被點擊‘) 34 },true); 35 36 </script> 37 </body> 38 </html>
同樣我們看一下後臺的打印結果
和冒泡流完全相反,從最不具體的元素接收到最具體的元素接收事件 body=>div=>button=>p
-
事件代理
在實際的開發當中,利用事件流的特性,我們可以使用一種叫做事件代理的方法。
<ul id="color-list"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul>
如果點擊頁面中的li元素,然後輸出li當中的顏色,我們通常會這樣寫:
1 var list_li = document.getElementsByTagName(‘li‘); 2 for (var i = 0; i < list_li.length; i++) { 3 list_li[i].addEventListener(‘click‘,foo,false); 4 } 5 function foo (e) { 6 let x = e.target; 7 console.log(x.innerHTML) 8 }
利用事件流的特性,我們只綁定一個事件處理函數也可以完成:
1 var list_ul = document.getElementById(‘color-list‘); 2 list_ul.addEventListener(‘click‘, foo, false); 3 function foo (e) { 4 let x = e.target; 5 if (x.nodeName == ‘LI‘) { 6 console.log(x.innerHTML) 7 } 8 }
-
冒泡還是捕獲?
對於事件代理來說,在事件捕獲或者事件冒泡階段處理並沒有明顯的優劣之分,但是由於事件冒泡的事件流模型被所有主流的瀏覽器兼容,從兼容性角度來說還是建議大家使用事件冒泡模型。
IE瀏覽器兼容
IE瀏覽器對addEventListener兼容性並不算太好,只有IE9以上可以使用。
要兼容舊版本的IE瀏覽器,可以使用IE的attachEvent函數
object.attachEvent(event, function)
兩個參數與addEventListener相似,分別是事件和處理函數,默認是事件冒泡階段調用處理函數。並且由於IE瀏覽器只支持事件冒泡,所以添加的程序都被添加到冒泡階段。要註意的是,寫事件名時候要加上"on"前綴("onload"、"onclick"等)。
區別
addEventListener與attachEvent除了參數個數以及第一個參數意義不同外。還有如下兩點:
- 事件處理程序的作用域不相同:addEventListener的作用域是元素本身,this指的是觸發元素。而attachEvent事件處理程序會在全局變量內運行,this指的是window,所以剛才的例子返回的結果是undefined,而不是元素id。
- 為一個事件添加多個事件處理程序時,執行順序不同:使用addEventListener時瀏覽器會按照添加順序執行,IE瀏覽器使用attachEvent時,如果添加的方法過多時,IE瀏覽器將不會按照順序執行。
-
阻止事件冒泡與阻止默認事件
阻止事件冒泡 stopPropagation() 方法
可以阻止事件冒泡,也可以阻止事件捕獲,也可以阻止處於目標階段
使用stopPaopagation()方法可以停止事件在DOM層次的傳播,不再派發事件。
1 <div id="p">parent 2 <div id="c">child</div> 3 </div> 4 <script type="text/javascript"> 5 var p = document.getElementById(‘p‘), 6 c = document.getElementById(‘c‘); 7 c.addEventListener(‘click‘, function (e) { 8 e.stopPropagation() 9 alert(‘子節點冒泡‘) //不再向上冒泡到父級 10 }, false); 11 p.addEventListener(‘click‘, function () { 12 alert(‘父節點冒泡‘)}, false); 13 </script>
阻止默認事件 event.preventDefault() 方法 (基本沒作用吧...很少有需求將默認事件取消掉吧)
event.preventDefault()
聊聊事件冒泡與事件捕獲