Javascript事件模型系列(一)事件及事件的三種模型

呂大豹發表於2013-08-18

一、開篇

         在學習javascript之初,就在網上看過不少介紹javascript事件的文章,畢竟是js基礎中的基礎,文章零零散散有不少,但遺憾的是沒有看到比較全面的系列文章。猶記得去年這個時候,參加百度的實習生面試,被問到事件模型,當時被問的一頭霧水,平時敲onclick敲的挺爽,卻沒有關注到事件模型的整體概念。這個週末難得清閒,決定就javascript中的事件模型寫個系列,算是對知識點的一個總結,也是對自己的一個交代。

         初步計劃分為以下幾個部分:

    ①   javascript事件的基本概念及基於原始、IE、DOM2的三種模型的異同點

    ②   javascript事件流介紹,捕獲-冒泡機制及事件委託機制

    ③   jquery中的事件監聽方式(bind、live、attachEvent、on)及異同點

    ④   javascript自定義事件

二、event簡介

         什麼是事件呢?直觀的說就是網頁上發生的事情,大部分是指使用者的滑鼠動作和鍵盤動作,如點選、移動滑鼠、按下某個鍵。為什麼說大部分呢,因為事件不單單隻有這兩部分,還有其他的例如document的load和unloaded。只不過我們更加關注的是使用者的操作。事件被封裝成一個event物件,包含了該事件發生時的所有相關資訊(event的屬性)以及可以對事件進行的操作(event的方法)。

         event長啥樣呢,來直觀的看一下,比如我點選頁面上一個按鈕,產生的event物件如下:

 

         可以看到是一個MouseEvent物件,包含了一系列屬性,如滑鼠點選的位置等。那麼敲擊鍵盤時產生的event物件和它一樣嗎?看看就知道:

    可以看到是一個KeyboardEvent物件,並且屬性跟上面的也不太一樣,如沒有clientX/Y,那是理所當然的啦,敲鍵盤怎麼能獲取到滑鼠的位置呢。

           若你有一點物件導向程式設計的基礎,看到這兩個類名應該會有所思考,MouseEvent、KeyboardEvent會不會是繼承自一個叫Event的類呢?恭喜你猜對了,確實如此。來看一下,我在window.onload監聽函式中列印出event物件如下:

    屬性少了很多,畢竟是父類嘛。若你想了解更多關於事件型別的內容,可以參考這裡,本文就不做更深的介紹。

三、event物件常用屬性、方法

1. 事件定位相關屬性

    這部分屬性平時用的還是挺多的,所以得著重介紹。如果你細細看了MouseEvent物件裡的屬性,一定發現了有很多帶X/Y的屬性,它們都和事件的位置相關。具體包括:x/y、clientX/clientY、pageX/pageY、screenX/screenY、layerX/layerY、offsetX/offset 六對。有點亂了吧,一個點選事件能有多少位置啊?不要著急,其實並不複雜,之所以能有這麼多是因為各瀏覽器廠商在版本更迭的時候產生了很多不一致。看下面的例子就明白各自的含義了:

在這裡移動滑鼠
x:   y:
clientX:   clientY:
screenX:   screenY:
offsetX:   offsetY:
pageX:   pageY:
layerX:   layerY:

    得出的結論如下:

    x/y與clientX/clientY值一樣,表示距瀏覽器可視區域(工具欄除外區域)左/上的距離;

    pageX/pageY,距頁面左/上的距離,它與clientX/clientY的區別是不隨滾動條的位置變化;

    screenX/screenY,距計算機顯示器左/上的距離,拖動你的瀏覽器視窗位置可以看到變化;

    layerX/layerY與offsetX/offsetY值一樣,表示距有定位屬性的父元素左/上的距離。

    之所以有那麼多值一樣的情況,就是由於瀏覽器相容的原因。那我們平時該如何使用呢?請看下面的表格,列出了各屬性的瀏覽器支援情況。(+支援,-不支援)

    offsetX/offsetY:W3C- IE+ Firefox- Opera+ Safari+ chrome+

    x/y:W3C- IE+ Firefox- Opera+ Safari+ chrome+

    layerX/layerY:W3C- IE- Firefox+ Opera- Safari+ chrome+

    pageX/pageY:W3C- IE- Firefox+ Opera+ Safari+ chrome+

    clientX/clientY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+

    screenX/screenY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+​

        說明:該表摘自其他文章,我未做全部驗證,但從最新版本的現代瀏覽器來看,這些屬性貌似是都支援了,為了更好的相容性,通常我們選擇W3C支援的就可以了。若你想看更加細緻的相關描述,請點選這裡

2.其他常用屬性

    target:發生事件的節點;

    currentTarget:當前正在處理的事件的節點,在事件捕獲或冒泡階段;

      timeStamp:事件發生的時間,時間戳。

    bubbles:事件是否冒泡。

    cancelable:事件是否可以用preventDefault()方法來取消預設的動作;

    keyCode:按下的鍵的值;

3. event物件的方法

    event. preventDefault()//阻止元素預設的行為,如連結的跳轉、表單的提交;

    event. stopPropagation()//阻止事件冒泡

    event.initEvent()//初始化新事件物件的屬性,自定義事件會用,不常用

    event. stopImmediatePropagation()//可以阻止掉同一事件的其他優先順序較低的偵聽器的處理,(我沒有用過)

四、事件的三種模型

           由於複雜的歷史原因,事件模型是不統一的,當然作為前端開發人員這種事情已經見怪不怪了。儘管W3C已經制定了DOM2標準來規範事件的定義,但由於頑固的IE6、7、8存在,我們還是得清楚IE的那一套定義。那麼來看看三種模型都有哪些吧。

1.     原始事件模型

    在原始事件模型中(也有說DOM0級),事件發生後沒有傳播的概念,沒有事件流。事件發生,馬上處理,完事,就這麼簡單。監聽函式只是元素的一個屬性值,通過指定元素的屬性值來繫結監聽器。書寫方式有兩種:

    ①   HTML程式碼中指定屬性值:<input type=”button” onclick=”func1()” />

    ②   在js程式碼中指定屬性值:document.getElementsByTagName(‘input’)[0].onclick = func1

    優點:所有瀏覽器都相容

    缺點:1)邏輯與顯示沒有分離;2)相同事件的監聽函式只能繫結一個,後繫結的會覆蓋掉前面的,如:a.onclick = func1; a.onclick = func2;將只會執行func2中的內容。3)無法通過事件的冒泡、委託等機制(後面系列會講到)完成更多事情。

    在當前web程式模組化開發以及更加複雜的邏輯狀況下,這種方式顯然已經落伍了,所以在真正專案中不推薦使用,平時寫點部落格小例子啥的倒是可以,速度比較快。

2.     IE事件模型

    在參考其他資料時,我有看到這樣的一句話“IE不把該物件傳入事件處理函式,由於在任意時刻只會存在一個事件,所以IE把它作為全域性物件window的一個屬性”,為求證其真偽,我用IE8執行了程式碼alert(window.event),結果彈出是null,說明該屬性已經定義,只是值為null(與undefined不同)。我想難道這個全域性物件的屬性是在監聽函式裡才加的?於是執行下面程式碼:

    window.onload = function (){alert(window.event);}

    setTimeout(function(){alert(window.event);},2000);

    結果第一次彈出【object event】,兩秒後彈出依然是null。由此可見IE是將event物件在處理函式中設為window的屬性,一旦函式執行結束,便被置為null了。IE的事件模型只有兩步,先執行元素的監聽函式,然後事件沿著父節點一直冒泡到document。冒泡機制後面系列會講,此處暫記。IE模型下的事件監聽方式也挺獨特,繫結監聽函式的方法是:attachEvent( "eventType","handler"),其中evetType為事件的型別,如onclick,注意要加’on’。解除事件監聽器的方法是 detachEvent("eventType","handler" )

    IE的事件模型已經可以解決原始模型的三個缺點,但其自己的缺點就是相容性,只有IE系列瀏覽器才可以這樣寫。

3.     DOM2事件模型

    此模型是W3C制定的標準模型,既然是標準,那大家都得按這個來,我們現在使用的現代瀏覽器(指IE6~8除外的瀏覽器)都已經遵循這個規範。W3C制定的事件模型中,一次事件的發生包含三個過程:

    (1)capturing phase:事件捕獲階段。事件被從document一直向下傳播到目標元素,在這過程中依次檢查經過的節點是否註冊了該事件的監聽函式,若有則執行。

    (2)target phase:事件處理階段。事件到達目標元素,執行目標元素的事件處理函式.

    (3)bubbling phase:事件冒泡階段。事件從目標元素上升一直到達document,同樣依次檢查經過的節點是否註冊了該事件的監聽函式,有則執行。

    所有的事件型別都會經歷captruing phase但是隻有部分事件會經歷bubbling phase階段,例如submit事件就不會被冒泡。 

    你可能會有疑問,為什麼是這個樣子的呢?流程有點太多了吧?事情的緣由還得從網景公司與微軟爭霸開始說起。在W3C的規範還沒有出生的時候,市場上已經有兩家強勁的瀏覽器廠商,產品分別是微軟的IE和網景的Netspace Navigator(後面簡稱NN),IE的事件模型上面已介紹,事件是可以冒泡的。然而NN卻不這麼認為,它的模型中,事件是從上往下走的,即只有捕獲階段。兩家都沒有誰對誰錯,因為按照他們的模型都可以完成事件的處理。然後W3C珊珊來遲,要制定標準,要統一,所以也就只能兩家的都採納,誰也不得罪,然後用標準制定者的口吻宣佈:W3C模型工作良好。從此天下太平。

    說遠了,趕緊來看看標準的事件監聽器該如何繫結:addEventListener("eventType","handler","true|false");其中eventType指事件型別,注意不要加‘on’字首,與IE下不同。第二個引數是處理函式,第三個即用來指定是否在捕獲階段進行處理,一般設為false來與IE保持一致,除非你有特殊的邏輯需求。監聽器的解除也類似:removeEventListner("eventType","handler","true!false");

        以上便是事件的三種模型,我們在開發的時候需要兼顧IE與非IE瀏覽器,所以註冊一個監聽器應該這樣寫:

var a = document.getElementById('a');
if(a.attachEvent){
    a.attachEvent('onclick',func);
}
else{
    a.addEventListener('click',func,false);
}

        感覺很麻煩吧?因此我們一般會藉助現有框架或類庫已經封裝好的,比如jQuery,後面將會介紹jQuery中強大的事件監聽方式。

    系列一到此結束,作者本人技術水平有限,文章內容都是自己的理解寫出來的,歡迎各路高手指點糾錯。

相關文章