關(guān)于前端事件的那些事兒

事件這個(gè)東西呢?是一個(gè)很神奇的東西,搞的我很煩 無(wú)論是在Android端還是前端。今天巧借這點(diǎn)時(shí)間呢?就準(zhǔn)備來(lái)寫一寫這個(gè)東西。先寫一篇前端的事件,然后再轉(zhuǎn)向Android端寫一篇Android端的事件 perfect...

一、 什么叫事件流

事件流的描述呢?很簡(jiǎn)單:就是描述從頁(yè)面接收到事件的順序。現(xiàn)今主流的兩大陣營(yíng)對(duì)此提出了兩種相反的事件流:ie提出的稱之為事件冒泡;Netscape communicator提出的則是事件捕獲。那么什么是事件冒泡和事件捕獲呢?

  • 事件冒泡

IE的事件流叫做事件冒泡,說(shuō)白了冒泡就是從嵌套最深的那個(gè)節(jié)點(diǎn)接收,然后逐級(jí)向上冒泡直到document的過(guò)程。用下面的html為例子:

<!DOCTYPE html>
<html>
<head>
    <title>我是測(cè)試</title>
</head>
<body>
    <div >click Me</div>
</body>
</html>

如果你單擊了div元素,那么這個(gè)click事件就這么傳遞:
1、 div
2、 body
3、 html
4、document
也就是說(shuō),當(dāng)我們點(diǎn)擊了div元素之后,click事件首先在div元素上面發(fā)生,這個(gè)元素就是我們單擊的元素。然后click事件就順著DOM樹向上傳播,在每一級(jí)上面都會(huì)發(fā)生,一直傳到document。


  • 事件捕獲

Netscape communicator團(tuán)隊(duì)提出了另一種事件流叫做事件捕獲。事件捕獲的過(guò)程呢?則是從document開始一直傳遞到div的過(guò)程:
1、document
2、 html
3、 body
4、 div
示意圖如下:


  • DOM的事件流

通常的事件流包括三個(gè)步驟:事件捕獲階段、處于目標(biāo)階段和事件冒泡階段。用前面的html表示,大概就是這樣。



事件在處于捕獲階段的時(shí)候呢?是不會(huì)接收到事件的,然后再目標(biāo)階段的時(shí)候,事件發(fā)生在div元素中,然后通過(guò)冒泡傳回文檔流

二、 事件的處理程序

事件呢?就是:用戶或者瀏覽器自身執(zhí)行的某種動(dòng)作。比如click,load和mouseover。而相應(yīng)某個(gè)事件的函數(shù)叫做事件處理程序。一般都是on開頭的,onclick就是click的處理程序、onload就是load的處理程序等等...
首先 我們直接用代碼來(lái)寫主要的幾種類型:

1、 html事件處理程序:

    <button id="myBtn1" onclick="showMessage()">我是 html事件處理程序</button>

    function showMessage(){
      alert("hello my friends!")
    }

2、 Dom0事件處理程序:

        <button id="myBtn2">我是dom0事件處理程序</button>

        var btn2 = document.getElementById("myBtn2");
        btn.onclick = function(){
          alert("hello my friends!");
        }
        btn.onclick = null;  //移除事件

3、 Dom2事件處理程序:

這里和前面不同,dom2定義了兩個(gè)方法:addEventListener()和removeEventListener()分別用來(lái)綁定和刪除事件。所有的dom節(jié)點(diǎn)都包含這個(gè)方法,并且它接受三個(gè)參數(shù):處理的事件名、作為事件處理的函數(shù)和一個(gè)布爾值。如果這個(gè)布爾值是true,表示在捕獲階段調(diào)用事件程序;如果是false,則表示在冒泡階段調(diào)用事件處理程序。

事件綁定如下:

    <button id="myBtn">我是dom2事件處理程序</button>

    var btn = document.getElementById("myBtn");
    btn.addEventListener("click",function(){
        alert("hello klivitam!")
    },false)

關(guān)于事件綁定和移除有這么幾個(gè)要注意的點(diǎn):

  • 事件多次綁定
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
    alert("hello klivitam!")
},false)

btn.addEventListener("click",function(){
    alert("123456")
},false)

如果重復(fù)進(jìn)行添加事件的話,這樣會(huì)先出按照順序 先顯示hello klivitam,然后再顯示123456。

  • 事件移除
    先看下面一段代碼
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click",function(){
            alert("hello klivitam!")
        },false)

        btn.removeEventListener("click",function(){
            alert("hello klivitam!")
        },false)

我們知道:利用addEventListener()綁定的事件,需要用removeEventListener()來(lái)將事件的監(jiān)聽移除,并且removeEventListener()的參數(shù)必須和addEventListener()的參數(shù)相同。這里我們雖然看似兩者是相同的,但是并不能移除。這是值得注意的,針對(duì)這種情況,我們應(yīng)該這么寫:

        var handle = function(){
            alert("hello klivitam!")
        }
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click",handle,false)

        btn.removeEventListener("click",handle,false)

3、 IE事件處理程序:

ie中存在和dom中想同的兩個(gè)方法:attachEvent和detachEvent。這兩個(gè)方法接受想同的兩個(gè)參數(shù):事件處理程序的名稱與事件處理程序函數(shù)。
首先我們先寫一個(gè)綁定的實(shí)例:

        var btn2 = document.getElementById("myBtn2");

        btn2.attachEvent("onclick",function(){
            alert(window === this);
        })

        btn2.attachEvent("onclick",function(){
            alert("boy");
        })

同樣和dom2的一樣的操作,這里只不過(guò)函數(shù)名變了。這里值得注意的是:window和this相等。當(dāng)點(diǎn)擊btn2元素的時(shí)候,首先會(huì)提示true,然后再提示boy。并且在移除代碼的時(shí)候,和dom2的操作一樣

        var btn2 = document.getElementById("myBtn2");

        var handle = function(){
            alert("hello klivitam!")
        }

        btn2.attachEvent("onclick",handle)

        btn2.detachEvent("onclick",handle)

4、 跨瀏覽器的事件處理程序:

前面說(shuō)了這么多方面的事件處理程序,那么我們?cè)谶M(jìn)行開發(fā)的時(shí)候很可能會(huì)需要適配很多瀏覽器。我們?cè)诿看问录壎ǖ臅r(shí)候,肯定要考慮到諸多版本,諸多內(nèi)核的瀏覽器。為了解決這種邏輯,我們就可以進(jìn)行封裝處理。我在這里是這樣進(jìn)行處理的:

        var EventUtils = {
            addHandler:function(ele,type,hander){
                if(ele.addEventListener){
                    ele.addEventListener(type,hander,false);
                }else if(ele.attachEvent){
                    ele.attachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = hander;
                }
            }

            removeHandler:function(ele,type,hander){
                if(ele.removeEventListener){
                    ele.removeEventListener(type,hander,false);
                }else if(ele.detachEvent){
                    ele.detachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = null;
                }
            }
        }

首先,我們?cè)诮壎ㄊ录臅r(shí)候呢?綁定事件的時(shí)候首先判斷是否支持dom2的事件處理程序,如果不支持,那么繼續(xù)判斷是否支持ie的事件處理程序,如若還不支持就只能用通用的事件處理程序。解綁的時(shí)候也是一樣,先判斷是否支持dom2事件處理程序,接著判斷ie的事件處理程序,最后在都不支持的情況下使用通用的事件處理程序。

我們?cè)谑褂玫臅r(shí)候就可以這樣寫一下就好了:

        var btn2 = document.getElementById("myBtn2");

        var handle = function(){
            alert("hello klivitam!");
        }

        EventUtils.addHandler(btn2,"click",hander);

        EventUtils.removeHandler(btn,"click",hander);

當(dāng)然這個(gè)封裝還室友點(diǎn)瑕疵的,比如不知道this的指向等等,當(dāng)然 我在后續(xù)的文章中會(huì)繼續(xù)的去討論封裝這個(gè)組件的。

三、 事件的對(duì)象

我們知道在觸發(fā)dom上的某個(gè)事件對(duì)象的時(shí)候,這個(gè)對(duì)象會(huì)包含著所有與事件有關(guān)的信息。包括事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。

  • dom的事件對(duì)象

兼容dom的瀏覽器會(huì)將一個(gè)event對(duì)象傳到事件處理程序中。例如:

        btn.addEventListener("click",function(e){
            console.log(e);
        },false)

        btn.onclick = function(e){
            console.log(e);
        };

上圖是我打印出來(lái)傳遞的event對(duì)象中所包含的值。我在這里呢?就簡(jiǎn)單的介紹幾個(gè)最基本的含義,剩下的其實(shí)大多數(shù)大家都能看懂。

屬性/方法 類型 讀寫 說(shuō)明
bubbles boolean 只讀 表明事件是否冒泡
cancelable boolean 只讀 表明是否可以取消事件的默認(rèn)行為
currentTarget Element 只讀 其事件的處理程序正在處理程序的那個(gè)元素
defaultPrevented boolean 只讀 為true表示已經(jīng)調(diào)用了preventDefault(dom3新增的)
detail Integer 只讀 與事件相關(guān)的詳細(xì)細(xì)節(jié)
eventPhase Integer 只讀 調(diào)用事件處理程序的階段:1表示捕獲,2處于目標(biāo),3處于冒泡
preventDefault() Function 只讀 取消事件的 默認(rèn)行為,如果cancelable為true才能使用這個(gè)方法
stopImmediatePropagation Function 只讀 取消事件的進(jìn)一步捕獲或冒泡,同時(shí)阻止任何事件處理程序被調(diào)用(dom3新增)
stopPropagation Function 只讀 取消事件的進(jìn)一步捕獲或者冒泡。如果cancelable為true的時(shí)候,才能使用這個(gè)方法
target Element 只讀 事件的目標(biāo)
trusted Boolean 只讀 為true表示事件是瀏覽器生成的。為false表示事件又開發(fā)者通過(guò)js創(chuàng)建的(dom3新增的)
type String 只讀 被觸發(fā)事件的類型
view abstractview 只讀 與事件相關(guān)聯(lián)的抽象視圖,等同于發(fā)生事件的window對(duì)象

上面表格中值得注意三點(diǎn)的就是:

  • this是始終等于currentTarget的值,而target則只包含事件的實(shí)際目標(biāo)

  • 當(dāng)cancalable為true的時(shí)候,我們可以通過(guò)preventDefault函數(shù)來(lái)取消其默認(rèn)行為

  • eventPhase可以用來(lái)判斷事件的狀態(tài),捕獲狀態(tài)為1,目標(biāo)對(duì)象上為2,冒泡上為3 。值得注意的是,當(dāng)eventPhase為2的時(shí)候this,target,currentTarget是始終相等的。

  • ie的事件對(duì)象

和訪問(wèn)dom中的event不同,ie中訪問(wèn)event對(duì)象有幾種不同的方式。來(lái)看下面的例子

btn.onclick = function(){
    var e = window.event;
    console.log(e);
};
btn.attachEvent("onclick",function(e){
    console.log(e)
})

同樣我也將ie事件的主要的幾個(gè)屬性也列在下面

屬性/方法 類型 讀寫 說(shuō)明
cancelBubble boolean 讀/寫 默認(rèn)值為false,但將其設(shè)置為true就可以取消事件(和dom中的stopPropagation()方法作用相同)
returnValue boolean 讀寫 默認(rèn)值為true,但將其設(shè)置為false就可以取消事件默認(rèn)行為(跟dom中的preventDefault()方法的作用相同)
srcElement element 只讀 事件的目標(biāo)(雷同dom中的target)
type String 只讀 被觸發(fā)的事件的類型

上面也值得我們注意的幾點(diǎn)是:

  • 在事件處理程序中(dom中),srcElemet屬性等于this,但是attachEvent this指向的是window
btn.onclick = function(){
    console.log(window.event.srcElement === this); // true
};
btn.attachEvent("onclick",function(e){
    console.log(e.srcElement === this) //false
})
  • 由前面表格所說(shuō)returnValue屬性相當(dāng)于DOM中的preventDefault()方法,能取消事件的默認(rèn)行為。在ie中,只要將returnValue設(shè)置成false,就可以組織默認(rèn)行為了。

  • 跨瀏覽器的事件對(duì)象

前面僅僅封裝了事件的綁定和解綁,但是我們考慮到我們有的時(shí)候可能會(huì)用到事件的event對(duì)象,前面的介紹可以知道ie和dom中會(huì)有些許不同,此時(shí)我們就可以對(duì)事件進(jìn)行再次封裝。代碼如下:

    var EventUtils = {
            addHandler:function(ele,type,hander){
                if(ele.addEventListener){
                    ele.addEventListener(type,hander,false);
                }else if(ele.attachEvent){
                    ele.attachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = hander;
                }
            }

            getEvent:function(e){
                return e?e:window.event;
            }

            getTarget:function(e){
                return e.target||e.srcElement;
            }

            preventDefault:function(e){
                if(e.preventDefault){
                    e.preventDefault()
                }else{
                    e.returnValue = false;
                }

            }

            removeHandler:function(ele,type,hander){
                if(ele.removeEventListener){
                    ele.removeEventListener(type,hander,false);
                }else if(ele.detachEvent){
                    ele.detachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = null;
                }
            }

            stopPropagation:function(e){
                if(e.stopPropagation){
                    e.stopPropagation();
                }else{
                    e.cancelBubble = true;
                }
            }
        }

這里呢?主要總結(jié)了事件冒泡的一些跨瀏覽器的兼容性問(wèn)題,當(dāng)然值得注意的是IE 不支持事件捕獲的,所以這個(gè)方法不是不支持事件捕獲的。

說(shuō)在最后

十一呢?去過(guò)漂流,也去爬山,然后腿也瘸了。之后在家看了幾天的書籍。怎么說(shuō)呢?領(lǐng)悟到活著的好處,也領(lǐng)悟到活蹦亂跳的快樂(lè)了。我已經(jīng)決心不在做一個(gè)秀才,突然有個(gè)瞬間覺(jué)得這個(gè)城市這么大 我卻沒(méi)去過(guò)幾處的感覺(jué)。
嗯!關(guān)于接下來(lái)幾個(gè)月的規(guī)劃呢?我會(huì)每月寫一篇書評(píng),每周寫一篇Android,寫一篇前端、寫一篇小程序的文章來(lái)鞭策自己不斷的向前。敬請(qǐng)期待吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容