1. 程式人生 > >一次關於js事件出發機制反常的解決記錄

一次關於js事件出發機制反常的解決記錄

add 行為 click事件 解除綁定 部分 win 窗口 www child

起因:正常情況下我點擊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即可,如果.bclass這種,以此類推。

我們經常能遇到阻止冒泡,但是阻止捕獲一般不會遇到,因為瀏覽器一般默認就給我們阻止了,只能說什麽情況都有啊,萬事還是得考慮周全。

一次關於js事件出發機制反常的解決記錄