一次關於js事件出發機制反常的解決記錄
起因:正常情況下我點擊s2時是先彈出我是children,再彈出我是father,但是卻出現了先彈出我是father,後彈出我是children的情況,這種情況是在和安卓app交互的h5頁面中出現的,本地測試沒有問題,但是在安卓打包的內嵌h5頁面就出現了問題。簡單化的代碼先展示出來。
html代碼如下
<div id="father" class="ss1">s1 <div id="children" class="ss2">s2 </div> </div>
事件綁定如下
$(‘#father‘).on(‘click‘,function(e) { alert(‘我是father‘) }) $(‘#children‘).on(‘click‘,function (e) { alert(‘我是children‘) e.stopPropagation(); })
借此問題,復習了一下js事件,先看一下幾個定義
先來看事件註冊
// IE以外的其他瀏覽器 // target :文檔節點、document、window 或 XMLHttpRequest。 // type :字符串,事件名稱,不含“on”,比如“click”、“mouseover”、“keydown”等。 // listener :實現了 EventListener 接口或者是 JavaScript 中的函數。// useCapture :是否使用捕捉,一般用 false,事件觸發時,會將一個 Event 對象傳遞給事件處理程序。 target.addEventListener(type,listener,useCapture);//添加 target.removeEventListener(type,listener,useCapture);//刪除
// IE瀏覽器 // target :文檔節點、document、window 或 XMLHttpRequest。 // type :字符串,事件名稱,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。 // listener :實現了 EventListener 接口或者是 JavaScript 中的函數。target.attachEvent(type, listener);//添加 target.detachEvent(type, listener);// 移除
兼容寫法
兼容後的方法 var func = function(){}; //例:addEvent(window,"load",func) function addEvent(elem, type, fn) { if (elem.attachEvent) { elem.attachEvent(‘on‘ + type, fn); return; } if (elem.addEventListener) { elem.addEventListener(type, fn, false); } } //例:removeEvent(window,"load",func) function removeEvent(elem, type, fn) { if (elem.detachEvent) { elem.detachEvent(‘on‘ + type, fn); return; } if (elem.removeEventListener) { elem.removeEventListener(type, fn, false); } }
獲取事件對象和事件源(觸發事件的元素)
function eventHandler(e){ //獲取事件對象 e = e || window.event;//IE和Chrome下是window.event FF下是e //獲取事件源 var target = e.target || e.srcElement;//IE和Chrome下是srcElement FF下是target }
事件委托
myTable.onclick = function () { e = e || window.event; var targetNode = e.target || e.srcElement; // 測試如果點擊的是TR就觸發 if (targetNode.nodeName.toLowerCase() === ‘tr‘) { alert(‘You clicked a table row!‘); } }
事件函數的解除綁定
和事件的綁定其實是相對應的,如果需要接觸事件的綁定,運行對應的函數就可以了。如果是原生JS綁定則對應運行removeEventListener()和detachEvent()。
如果是jQuery的bind()和delegate()綁定,也是存在對應的解綁函數用以清除註冊事件,比如unbind()和undelegate()。
看一個代碼示例:
var EventUtil = { //註冊 addHandler: function(element, type, handler){ if (element.addEventListener){ element.addEventListener(type, handler, false); } else if (element.attachEvent){ element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, //移除註冊 removeHandler: function(element, type, handler){ if (element.removeEventListener){ element.removeEventListener(type, handler, false); } else if (element.detachEvent){ element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } } };
再來看看事件流
幾個概念
捕獲階段:事件對象通過目標的祖先從傳播窗口到目標的父。這個階段也被稱為捕獲階段。
目標階段:本次活動對象到達事件對象的事件的目標。這個階段也被稱為目標階段。如果事件類型指示事件不起泡,則在完成此階段後,事件對象將停止。
冒泡階段:事件對象通過目標的祖先中傳播以相反的順序,開始與目標的父和與所述結束窗口。這個階段也被稱為冒泡階段。
默認行為:事件通常由實現作為用戶操作的結果分派,以響應任務的完成,或者在異步活動(例如網絡請求)期間發信號通知進度。有些事件可以用來控制下一個實現可能采取的行為(或者撤銷實現已經采取的行動)。這個類別中的事件被認為是可取消的,他們取消的行為被稱為他們的默認行為。
取消事件:可取消的事件對象可以與一個或多個“默認動作”相關聯。要取消事件,請調用該preventDefault()
方法。
一個圖片
再上個小demo
<ul> <li>點我試試</li> </ul> <div id="s1" class="ss1">s1 <div id="s2" class="ss2">s2</div> </div>
var ul = document.getElementsByTagName(‘ul‘)[0]; var li = document.getElementsByTagName(‘li‘)[0];
element.addEventListener(event, function, useCapture) document.addEventListener(‘click‘,function(e){console.log(‘document clicked‘)},true);//第三個參數為true使用捕獲,false為冒泡,false為默認 ul.addEventListener(‘click‘,function(e){console.log(‘ul clicked‘)},true); li.addEventListener(‘click‘,function(e){console.log(‘li clicked‘)},true); //IE低版本兼容寫法 li.attachEvent(‘onclick‘,function(event){ debugger console.log(‘li clicked‘); event.cancelBubble=true; }); s1.addEventListener(‘click‘,function () { console.log(‘s1 捕獲方式‘) },true) s1.addEventListener(‘click‘,function () { console.log(‘s1 冒泡方式‘) },false) s2.addEventListener(‘click‘,function (e) { console.log(‘s2 捕獲方式‘) // e.stopPropagation(); },true) s2.addEventListener(‘click‘,function () { console.log(‘s2 冒泡方式‘) },false)
點擊li時,打印 依次為
ul clicked li clicked
點擊s1時,打印依次為
s1 捕獲方式 s1 冒泡方式
點擊s2時,打印依次為
s1 捕獲方式 s2 捕獲方式 s2 冒泡方式 s1 冒泡方式
處理事件冒泡和默認事件
1、e.preventDefault()
var a = document.getElementById("testA"); a.onclick =function(e){ if(e.preventDefault){ e.preventDefault();// }else{ window.event.returnValue = false;//IE //註意:這個地方是無法用return false代替的 //return false只能取消元素 } }
2、return false javascript的return false只會阻止默認行為,而是用jQuery的話則既阻止默認行為又防止對象冒泡。
//原生js,只會阻止默認行為,不會停止冒泡 var a = document.getElementById("testA"); a.onclick = function(){ return false;//當然 也阻止了事件本身 }; //既然return false 和 e.preventDefault()都是一樣的效果,那它們有區別嗎?當然有。 //僅僅是在HTML事件屬性 和 DOM0級事件處理方法中 才能通過返回 return false 的形式組織事件宿主的默認行為。
1 //jQuery,既阻止默認行為又停止冒泡 2 $("#testA").on(‘click‘,function(){ 3 return false;//當然 也阻止了事件本身 4 });
總結使用方法
當需要停止冒泡行為時
function stopBubble(e) { //如果提供了事件對象,則這是一個非IE瀏覽器 if ( e && e.stopPropagation ){ e.stopPropagation(); //因此它支持W3C的stopPropagation()方法 }else{ window.event.cancelBubble = true; //否則,我們需要使用IE的方式來取消事件冒泡 } }
當需要阻止默認事件時
function stopDefault( e ) { if ( e && e.preventDefault ){ e.preventDefault(); //阻止默認瀏覽器動作(W3C) }else { window.event.returnValue = false; //IE中阻止函數器默認動作的方式 } return false; }
最後的解決方法:
讓我們回顧一下最初的問題,可能部分瀏覽器把事件的useCapture默認為true,導致點擊子元素時父元素的事件先響應了,於是我的辦法是在父元素的事件裏進行判斷
比如容器為
#a,
動態插入的元素為#b,
在#a上監聽click事件,判斷event.target.id是不是等於b即可,如果.b
class這種,以此類推。
我們經常能遇到阻止冒泡,但是阻止捕獲一般不會遇到,因為瀏覽器一般默認就給我們阻止了,只能說什麽情況都有啊,萬事還是得考慮周全。
一次關於js事件出發機制反常的解決記錄