糯米文學吧

位置:首頁 > 設計 > 網頁設計

深入分析原生JavaScript事件分析

JQuery這種Write Less Do More的框架,用多了難免會對原生js眼高手低。

深入分析原生JavaScript事件分析

小菜其實不想寫這篇博客,貌似很初級的樣子,但是看到網絡上連原生js事件綁定和解除都説不明白,還是決定科普一下了。

首先聲明,小菜懂的也不是很多,只是把我的思路和大家分享一下。

DOM0事件模型

事件模型在不斷髮展,早期的事件模型稱為DOM0級別。

DOM0事件模型,所有的瀏覽器都支持。

直接在dom對象上註冊事件名稱,就是DOM0寫法,比如:

複製代碼 代碼如下:

lementById("test")ick = function(e){};

意思就是註冊一個onclick事件。當然,它和這種寫法是一個意思:

複製代碼 代碼如下:

lementById("test")["onmousemove"] = function(e){};

這沒什麼,只不過是兩種訪問js對象屬性的方法,[]的形式主要是為了解決屬性名不是合法的標識符,比如:object.123肯定報錯,但是object["123"]就避免了這個問題,與此同時,[]的寫法,也把js寫活了,用字符串表示屬性名稱,可以在運行時動態綁定事件。

言歸正傳,事件被觸發時,會默認傳入一個參數e,表示事件對象,通過e,我們可以獲取很多有用的信息,比如點擊的座標、具體觸發該事件的dom元素等等。

基於DOM0的事件,對於同一個dom節點而言,只能註冊一個,後邊註冊的同種事件會覆蓋之前註冊的。例如:

複製代碼 代碼如下:

var btn = lementById("test");

usemove = function(e){

alert("ok");

};

btn["onmousemove"] = function(e){

alert("ok1");

};

結果會輸出ok1。

接下來再説説this。事件觸發時,this就是指該事件在哪個dom對象上觸發。例如:

複製代碼 代碼如下:

var btn = lementById("test");

usemove = function(e){

alert();

};

結果輸出test。因為事件就是在id為test的dom節點上註冊的,事件觸發時,this當然代表這個dom節點,可以理解為事件是被這個dom節點調用的。

所以,想解除事件就相當簡單了,只需要再註冊一次事件,把值設成null,例如:

複製代碼 代碼如下:

var btn = lementById("test");

ick = function(e){

alert("ok");

};

ick = null;

原理就是最後註冊的事件要覆蓋之前的,最後一次註冊事件設置成null,也就解除了事件綁定。

事情還沒結束,DOM0事件模型還涉及到直接寫在html中的事件。例如:

複製代碼 代碼如下:

通過這種方式註冊的事件,同樣遵循覆蓋原則,同樣只能註冊一個,最後一個生效。

區別就是,這樣註冊的事件,相當於動態調用函數(有點eval的意思),因此不會傳入event對象,同時,this指向的是window,不再是觸發事件的dom對象。

DOM2事件模型

DOM2事件模型相對於DOM0,小菜僅僅瞭解如下兩點:

· DOM2支持同一dom元素註冊多個同種事件。

· DOM2新增了捕獲和冒泡的概念。

DOM2事件通過addEventListener和removeEventListener管理,當然,這是標準。

但IE8及其以下版本瀏覽器,自娛自樂,搞出了對應的attachEvent和detachEvent,由於小菜才疏學淺,本文不做討論。

addEventListener當然就是註冊事件,她有三個參數,分別為:"事件名稱", "事件回調", "捕獲/冒泡"。舉個例子:

複製代碼 代碼如下:

var btn = lementById("test");

ventListener("click", function(e){

alert("ok");

}, false);

事件名稱就不用多説了,相比DOM0,去掉了前邊的on而已。

事件回調也很好理解,事件觸發了總得通知你吧!回調時和DOM0一樣,也會默認傳入一個event參數,同時this是指觸發該事件的dom節點。

最後一個參數是布爾型,true代表捕獲事件,false代表冒泡事件。其實很好理解,先來個示意圖:

意思就是説,某個元素觸發了某個事件,最先得到通知的是window,然後是document,依次而入,直到真正觸發事件的那個元素(目標元素)為止,這個過程就是捕獲。接下來,事件會從目標元素開始起泡,再依次而出,直到window對象為止,這個過程就是冒泡。

為什麼要這樣設計呢?這貌似是由於深厚的歷史淵源,小菜也不怎麼了解,就不亂説了。

由此可以看出,捕獲事件要比冒泡事件先觸發。

假設有這樣的'html結構:

複製代碼 代碼如下:

然後我們在外層div上註冊兩個click事件,分別是捕獲事件和冒泡事件,代碼如下:

複製代碼 代碼如下:

var btn = lementById("test");

//捕獲事件

ventListener("click", function(e){

alert("ok1");

}, true);

//冒泡事件

ventListener("click", function(e){

alert("ok");

}, false);

最後,點擊內層的div,先彈出ok1,後彈出ok。結合上邊的原理圖,外層div相當於圖中的body,內層div相當於圖中最下邊的div,證明了捕獲事件先執行,然後執行冒泡事件。

為什麼要強調點擊內層的div呢?因為真正觸發事件的dom元素,必須是內層的,外層dom元素才有機會模擬捕獲事件和冒泡事件,從原理圖上就看出了。

如果在真正觸發事件的dom元素上註冊捕獲事件和冒泡事件呢?

html結構同上,js代碼如下:

複製代碼 代碼如下:

var btnInner = lementById("testInner");

//冒泡事件

ventListener("click", function(e){

alert("ok");

}, false);

//捕獲事件

ventListener("click", function(e){

alert("ok1");

}, true);

當然還是點擊內層div,結果是先彈出ok,再彈出ok1。理論上應該先觸發捕獲事件,也就是先彈出ok1,但是這裏比較特殊,因為我們是在真正觸發事件的dom元素上註冊的事件,相當於在圖中的div上註冊,由圖可以看出真正觸發事件的dom元素,是捕獲事件的終點,是冒泡事件的起點,所以這裏就不區分事件了,哪個先註冊,就先執行哪個。本例中,冒泡事件先註冊,所以先執行。

這個道理適用於多個同種事件,比如説一下子註冊了3個冒泡事件,那麼執行順序就按照註冊的順序來,先註冊先執行。例如:

複製代碼 代碼如下:

var btnInner = lementById("testInner");

ventListener("click", function(e){

alert("ok");

}, false);

ventListener("click", function(e){

alert("ok1");

}, false);

ventListener("click", function(e){

alert("ok2");

}, false);

結果當然是依次彈出ok、ok1、ok2。

為了進一步理解事件模型,還有一種場景,假如説外層div和內層div同時註冊了捕獲事件,那麼點擊內層div時,外層div的事件一定是先觸發的,代碼如下:

複製代碼 代碼如下:

var btn = lementById("test");

var btnInner = lementById("testInner");

ventListener("click", function(e){

alert("ok");

}, true);

ventListener("click", function(e){

alert("ok1");

}, true);

結果是先彈出ok1。

假如外層div和內層div都是註冊的冒泡事件,點擊內層div時,一定是內層div事件先執行,原理相同。

細心的讀者會發現,對於div嵌套的情況,如果點擊內層的div,外層的div也會觸發事件,這貌似會有問題!

點擊的明明是內層div,但是外層div的事件也觸發了,這的確是個問題。

其實,事件觸發時,會默認傳入一個event對象,前邊提過了,這個event對象上有一個方法:stopPropagation,通過此方法,可以阻止冒泡,這樣外層div就接收不到事件了。代碼如下:

複製代碼 代碼如下:

var btn = lementById("test");

var btnInner = lementById("testInner");

ventListener("click", function(e){

alert("ok1");

}, false);

ventListener("click", function(e){

//阻止冒泡

Propagation();

alert("ok");

}, false);

終於要説説怎麼解除事件了。解除事件語法:veEventListener("事件名稱", "事件回調", "捕獲/冒泡");

這和綁定事件的參數一樣,詳細説明下:

· 事件名稱,就是説解除哪個事件唄。

· 事件回調,是一個函數,這個函數必須和註冊事件的函數是同一個。

· 事件類型,布爾值,這個必須和註冊事件時的類型一致。

也就是説,名稱、回調、類型,三者共同決定解除哪個事件,缺一不可。舉個例子:

複製代碼 代碼如下:

var btn = lementById("test");

//將回調存儲在變量中

var fn = function(e){

alert("ok");

};

//綁定

ventListener("click", fn, false);

//解除

veEventListener("click", fn, false);

要想註冊過的事件能夠被解除,必須將回調函數保存起來,否則無法解除。

DOM0與DOM2混用

事情本來就很亂了,這又來個混合使用,還讓不讓人活了。。。

別怕,混合使用完全沒問題,DOM0模型和DOM2模型各自遵循自己的規則,互不影響。

整體上來説,依然是哪個先註冊,哪個先執行,其他就沒什麼了。

後記

至此,原生js事件已經講的差不多了,小菜僅僅知道這些而已,歡迎讀者補充其他知識點。

在實際應用中,真正的行家不會傻傻的真的註冊這麼多事件,一般情況下,只需在最外層dom元素註冊一次事件,然後通過捕獲、冒泡機制去找到真正觸發事件的dom元素,最後根據觸發事件的dom元素提供的信息去調用回調。

也就是説,行家會自己管理事件,而不依賴瀏覽器去管理,這樣即可以提高效率,又保證了兼容性,JQuery不就是這麼做的嘛~

好了,教程到此結束,希望對讀者有所幫助!