原生JavaScript事件詳解

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

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

     首先聲明,小菜懂的也不是不少,只是把個人思路和你們分享一下。網絡

 

DOM0事件模型框架

 

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

     DOM0事件模型,全部的瀏覽器都支持。函數

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

1 document.getElementById("test").onclick = function(e){};

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

1 document.getElementById("test")["onmousemove"] = function(e){};

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

     言歸正傳,事件被觸發時,會默認傳入一個參數e,表示事件對象,經過e,咱們能夠獲取不少有用的信息,好比點擊的座標、具體觸發該事件的dom元素等等。code

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

1 var btn = document.getElementById("test");
2 
3 btn.onmousemove = function(e){
4   alert("ok");
5 };
6 
7 btn["onmousemove"] = function(e){
8   alert("ok1");
9 };

     結果會輸出ok1。

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

1 var btn = document.getElementById("test");
2 
3 btn.onmousemove = function(e){
4   alert(this.id);
5 };

     結果輸出test。由於事件就是在id爲test的dom節點上註冊的,事件觸發時,this固然表明這個dom節點,能夠理解爲事件是被這個dom節點調用的。

     因此,想解除事件就至關簡單了,只須要再註冊一次事件,把值設成null,例如:

1 var btn = document.getElementById("test");
2 
3 btn.onclick = function(e){
4   alert("ok");
5 };
6 
7 btn.onclick = null;

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

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

1 <div id="test" class="test" onclick="exec();" ></div>

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

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

 

DOM2事件模型

 

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

 

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

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

 

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

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

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

1 var btn = document.getElementById("test");
2 
3 btn.addEventListener("click", function(e){
4   alert("ok");
5 }, false);

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

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

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

 

 

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

     爲何要這樣設計呢?這貌似是因爲深厚的歷史淵源,小菜也不怎麼了解,就不亂說了。

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

     假設有這樣的html結構:

1 <div id="test" class="test">
2   <div id="testInner" class="test-inner"></div>
3 </div>

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

 1 var btn = document.getElementById("test");
 2 
 3 //捕獲事件
 4 btn.addEventListener("click", function(e){
 5   alert("ok1");
 6 }, true);
 7 
 8 //冒泡事件
 9 btn.addEventListener("click", function(e){
10   alert("ok");
11 }, false);

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

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

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

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

 1 var btnInner = document.getElementById("testInner");
 2 
 3 //冒泡事件
 4 btnInner.addEventListener("click", function(e){
 5   alert("ok");
 6 }, false);
 7 
 8 //捕獲事件
 9 btnInner.addEventListener("click", function(e){
10   alert("ok1");
11 }, true);

     固然仍是點擊內層div,結果是先彈出ok,再彈出ok1。理論上應該先觸發捕獲事件,也就是先彈出ok1,可是這裏比較特殊,由於咱們是在真正觸發事件的dom元素上註冊的事件,至關於在圖中的div上註冊,由圖能夠看出真正觸發事件的dom元素,是捕獲事件的終點,是冒泡事件的起點,因此這裏就不區分事件了,哪一個先註冊,就先執行哪一個。本例中,冒泡事件先註冊,因此先執行。

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

 1 var btnInner = document.getElementById("testInner");
 2 
 3 btnInner.addEventListener("click", function(e){
 4   alert("ok");
 5 }, false);
 6 
 7 btnInner.addEventListener("click", function(e){
 8   alert("ok1");
 9 }, false);
10 
11 btnInner.addEventListener("click", function(e){
12   alert("ok2");
13 }, false);

     結果固然是依次彈出ok、ok一、ok2。

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

 1 var btn = document.getElementById("test");
 2 var btnInner = document.getElementById("testInner");
 3 
 4 btnInner.addEventListener("click", function(e){
 5   alert("ok");
 6 }, true);
 7 
 8 btn.addEventListener("click", function(e){
 9   alert("ok1");
10 }, true);

     結果是先彈出ok1。

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

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

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

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

 1 var btn = document.getElementById("test");
 2 var btnInner = document.getElementById("testInner");
 3 
 4 btn.addEventListener("click", function(e){
 5   alert("ok1");
 6 }, false);
 7 
 8 btnInner.addEventListener("click", function(e){
 9   //阻止冒泡
10 e.stopPropagation();
11   alert("ok");
12 }, false);

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

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

 

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

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

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

 

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

 1 var btn = document.getElementById("test");
 2 //將回調存儲在變量中
 3 var fn = function(e){
 4   alert("ok");
 5 };
 6 //綁定
 7 btn.addEventListener("click", fn, false);
 8 
 9 //解除
10 btn.removeEventListener("click", fn, false);

     要想註冊過的事件可以被解除,必須將回調函數保存起來,不然沒法解除。

 

DOM0與DOM2混用

 

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

     別怕,混合使用徹底沒問題,DOM0模型和DOM2模型各自遵循本身的規則,互不影響。

     總體上來講,依然是哪一個先註冊,哪一個先執行,其餘就沒什麼了。

 

後記

 

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

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

     也就是說,行家會本身管理事件,而不依賴瀏覽器去管理,這樣便可以提升效率,又保證了兼容性,JQuery不就是這麼作的嘛~

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

     手抽筋中。。。

相關文章
相關標籤/搜索