基於事件冒泡、事件捕獲和事件委托詳解
事件冒泡、事件捕獲和事件委托
在javascript裡,事件委托是很重要的一個東西,事件委托依靠的就是事件冒泡和捕獲的機制,我先來解釋一下事件冒泡和事件捕獲:
事件冒泡會從當前觸發的事件目標一級一級往上傳遞,依次觸發,直到document為止。
事件捕獲會從document開始觸發,一級一級往下傳遞,依次觸發,直到真正事件目標為止。
這麼說是不是很抽象,其實就像我敲擊瞭一下鍵盤,我在敲擊鍵盤的同時,我是不是也敲擊瞭這臺電腦,我寫個例子大傢就明白瞭:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <style type="text/css"> #box1 { width: 300px; height: 300px; background: blueviolet; } #box2 { width: 200px; height: 200px; background: aquamarine; } #box3 { width: 100px; height: 100px; background: tomato; } div { overflow: hidden; margin: 50px auto; } </style> <body> <div id="box1"> <div id="box2"> <div id="box3"></div> </div> </div> <script> function sayBox3() { console.log('你點瞭最裡面的box'); } function sayBox2() { console.log('你點瞭最中間的box'); } function sayBox1() { console.log('你點瞭最外面的box'); } // 事件監聽,第三個參數是佈爾值,默認false,false是事件冒泡,true是事件捕獲 document.getElementById('box3').addEventListener('click', sayBox3, false); document.getElementById('box2').addEventListener('click', sayBox2, false); document.getElementById('box1').addEventListener('click', sayBox1, false); </script> </body> </html>
我們畫瞭三個box,結構是父子關系,分別綁定瞭打印事件,現在我們來點擊最中間的紅色box:
我們發現,我們僅僅是點擊瞭紅色的box,但是綠色和紫色的box也被觸發瞭打印事件,觸犯順序是 紅色>綠色>紫色,這種現象就是事件冒泡瞭。
我們再試試事件捕獲,把上面代碼裡監聽事件的第三個參數改為true,然後點擊紅色的box:
我們還是隻點擊最中間的紅色box,和上一次一樣,也是三個box都觸發瞭事件,但是順序反過來瞭,紫色>綠色>紅色,這種現象稱為事件捕獲。
通過上面的例子,應該很容易就理解瞭事件冒泡和事件捕獲,我們平時都是默認冒泡的,冒泡是一直冒到document根文檔為止。
現在來談談事件委托,事件委托又稱之為事件代理,我們通過一個通俗的例子來解釋:
有三個同事預計會在周一收到快遞,為瞭簽收快遞,有兩種辦法:1.三個人在公司門口等快遞;2.委托給前臺MM代為簽收。現實當中,我們大都采用委托的方案(公司也不會容忍那麼多員工站在門口就為瞭等快遞)。前臺MM收到快遞後,她會判斷收件人是誰,然後按照收件人的要求簽收,甚至代為付款。這種方案還有一個優勢,那就是即使公司裡來瞭新員工(不管多少),前臺MM也會在收到寄給新員工的快遞後核實並代為簽收(可以給暫時不存在的節點也綁定上事件)。
我們再舉另一個例子:
現在有一個ul,ul裡又有100個li,我想給這100個li都綁定一個點擊事件,我們一般可以通過for循環來綁定,但是要是有1000個li呢? 為瞭提高效率和速度,所以我們這時可以采用事件委托,隻給ul綁定一個事件,根據事件冒泡的規則,隻要你點瞭ul裡的每一個li,都會觸發ul的綁定事件,我們在ul綁定事件的函數裡通過一些判斷,就可以給這100li都觸發點擊事件瞭。
具體怎麼實現,看代碼:
// 這裡不講IE,結尾再說 function clickLi() { alert('你點擊瞭li'); } document.getElementById('isUl').addEventListener('click', function(event) { // 每一個函數內都有一個event事件對象,它有一個target屬性,指向事件源 var src = event.target; // 我們判斷如果target事件源的節點名字是li,那就執行這個函數 // target裡面的屬性是非常多的,id名、class名、節點名等等都可以取到 if(src.nodeName.toLowerCase() == 'li') { clickLi() ; } });
這樣我們就通過給ul綁定一個點擊事件,讓所有的li都觸發瞭函數。
那如果想給不同的li綁定不同的函數怎麼辦?
假設有3個li,我們先寫3個不同的函數,再給3個li設置不同的id名,通過判斷id名是不是就能給不同的li綁定不同的函數啦:
<body> <ul id="isUl"> <li id="li01">1</li> <li id="li02">2</li> <li id="li03">3</li> </ul> <script> function clickLi01() { alert('你點擊瞭第1個li'); } function clickLi02() { alert('你點擊瞭第2個li'); } function clickLi03() { alert('你點擊瞭第3個li'); } document.getElementById('isUl').addEventListener('click', function(event) { var srcID = event.target.id; if(srcID == 'li01'){ clickLi01(); }else if(srcID == 'li02') { clickLi02(); }else if(srcID == 'li03') { clickLi03(); } }); </script> </body>
這就是所謂的事件委托,通過監聽一個父元素,來給不同的子元素綁定事件,減少監聽次數,從而提升速度。
那麼,能不能阻止元素的事件冒泡呢,答案是可以的。
一開始那個例子,假如我們真的隻想點擊最裡面的那個紅色box,不想另外兩個box的事件被觸發,我們可以在給紅色box綁定事件的函數裡這麼寫:
function sayBox3(event) { // 阻止冒泡 event.stopPropagation(); console.log('你點瞭最裡面的box'); } document.getElementById('box3').addEventListener('click', sayBox3, false);
這樣我們再點擊紅色的box,那就隻會觸發它本身的事件啦。
那阻止冒泡有沒有實際用途呢?答案是有的,我們看這個例子:
這是一個模態框,現在的需求是當我們點擊紅色的按鈕需要跳轉頁面,然後點擊白色的對話框不需要任何反應,點其它任何地方就關閉這個模態框。
這裡就需要用到阻止冒泡瞭,紅色的按鈕是白色對話框的子元素,白色對話框又是這整個模態框的子元素,我們給模態框加上一個點擊事件關閉,然後給紅色的按鈕加上一個點擊事件跳轉,這時就產生瞭一個問題,隻要點擊白色的對話框,由於冒泡機制,這個模態框也會關閉,實際上我們並不想點擊白色的對話框有任何反應,這時我們就給這個白色的對話框綁定一個點擊事件,函數裡寫上event.stopPropagation();,這樣就OK瞭。
關於IE
老版本的IE存在兼容問題,根本不支持addEventListener()的添加事件和removeEventListener()的刪除事件,它有自己的監聽方法:
// 添加事件,事件流固定為冒泡 attachEvent(事件名,事件處理函數) // 刪除事件 detachEvent(事件名,事件處理函數)
還有IE裡的事件對象是window.event,事件源是srcElement,阻止冒泡寫法也不一樣:
function() { // IE裡阻止冒泡 window.event.cancelBubble = true; // IE裡獲取事件源的id var srcID = window.event.srcElement.id; } function(event) { // 非IE裡阻止冒泡 event.stopPropagation(); // 非IE裡獲取事件源的id var srcID = event.target.id; }
補充
關於js的瀏覽器兼容問題,一般用能力檢測來解決,if(){}else{}
我們平時工作一般都是用jquery,IE這些特殊情況早就幫我們做好兼容啦。
jquery在1.7的版本之後,最流行的事件監聽方法是$(元素).on(事件名,執行函數),它還有一種事件委托的寫法$(委托給哪個元素).on(事件名,被委托的元素,執行函數)
最後說一點,如果元素被阻止冒泡瞭,千萬別去用事件委托的方式監聽事件,因為事件委托的原理是利用事件冒泡,當冒泡被阻止,就無法監聽瞭。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 一起深入理解js中的事件對象
- js事件流、事件委托與事件階段實例詳解
- JavaScript阻止事件冒泡的方法
- Vue中addEventListener() 監聽事件案例講解
- JavaScript事件的委托(代理)的用法示例詳解