JavaScript 與 HTML 之間的交互是經過事件實現的。事件,就是文檔或者瀏覽器窗口中發生的一些特定的交互瞬間。可使用偵聽器(或處理程序)來預約事件,以便事件發生時執行相應的代碼。相似於設計模式中的觀察員模式。支持頁面的行爲與頁面的外觀之間的鬆散耦合。javascript
事件流描述的是從頁面中接收事件的順序。html
IE和 Netscape 開發團隊竟然提出了差很少是徹底相反的事件流的概念。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕獲流。java
1.事件冒泡node
IE的事件流叫作事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)。windows
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>
上例中,若是你點擊了頁面中的div 元素, 那麼這個click 事件會按照以下順序傳播:設計模式
也就是說,click事件首先在<div> 元素上發生,而這個元素就是咱們單機的元素。而後,click事件沿着DOM樹向上傳播,在每一級節點上都會發生,知道傳播到 document 對象。瀏覽器
全部現代瀏覽器都支持事件冒泡,但在具體實現上還有一些差異。IE 5.5 及更早的版本中的事件冒泡會跳過<html> 元素。IE九、firefox、 Chrome 和 Safari 則將事件一直冒泡到 window 對象。緩存
2.事件捕獲app
Netscape Communicator 團隊提出的另外一種事件流叫作事件捕獲(event capturing)。事件波活的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件事件捕獲的用意在於它事件到達預約目標以前捕獲它。仍然以上面的html 頁面做爲演示事件捕獲的例子。那麼單擊<div>元素就會如下列順序除法click 事件。框架
在事件捕獲過程當中,document 對象首先接收到click 事件,而後事件沿着 DOM 樹依次向下,一直傳播到事件的實際目標,即<div> 元素。
雖然事件捕獲是 Netscape Communicator 惟一支持的事件流模型,但IE九、Safari、Chrome、Opera 和 Firefox 目前也支持這種事件流模型。進管DOM2 及事件規範要求事件應該從document 對象開始傳播,但這些瀏覽器都是從 window 對象開始捕獲事件的。
因爲老版本的瀏覽器不支持,所以不多有人使用事件捕獲。因此建議使用事件冒泡,在有特殊須要是在使用事件捕獲。
3.DOM事件流
DOM2級事件 規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件作出相應。
事件就是用戶或瀏覽器自身執行的某種動做。諸如 click、load、mouseover,都是事件的名字,而相應某個事件的函數就叫作事件處理程序(或事件偵聽器)。事件處理程序的名字以on 開頭,所以 click 事件的事件處理程序就是 onclick,以此類推。
1.某個元素支持的每種事件,均可以使用一個與相應事件處理程序同名的HTML 特性來制定。這個特性的值應該是可以執行的 JavaScript 代碼。
<div id="myDiv" onclick="alert('Clicked')">Click Me</div>
也能夠調用其餘地方定義的腳本:
<div id="myDiv" onclick="showMessage()">Click Me</div> <script type="text/javascript"> function showMessage(){ alert("Hello world!"); } </script>
在HTML 中指定事件處理程序存在一個時差問題:由於用戶可能會在HTML 元素一出如今頁面上就觸發相應的事件,但當時的事件可能尚不具有執行條件。如上例中,假設代碼尚未加載進來用戶就點擊了div,就會引起錯誤。爲此,不少HTML 事件處理程序都會被封裝在一個try-catch 塊中,以便錯誤不會浮出水面。
<div id="myDiv" onclick="try{showMessage();}catch(ex){}">Click Me</div>
還有一個缺點是HTML 與 JavaScript 代碼緊密耦合。若是要更換事件處理程序,就要改動兩個地方:HTML 代碼 和 JavaScript 代碼。這正是許多開發人員摒棄HTML 事件處理程序,轉而使用JavaScript 制定事件處理程序的緣由。
2.DOM0 級事件處理程序
每一個元素(包括window 和 document)都有本身的事件處理程序屬性,這些屬性一般所有小寫,例如:onclick。將這種屬性設置爲一個函數,就能夠指定事件處理程序。
var div = document.getElementById("myDiv");
div.onclick = function(){
alert("click");
}
使用DOM0 級方法制定的事件處理程序被認爲是元素的方法。所以這時候的事件處理程序是在元素的做用域中運行,因此承諾須中的this 引用當前元素。
var div = document.getElementById("myDiv"); div.onclick = function(){ alert(this.id); //myDiv }
能夠經過將事件處理程序屬性的值設置爲null 來刪除經過DOM0 級方法制定的事件處理程序。
div.onclick = null;
3.DOM2 級事件處理程序
DOM2級事件 定義了兩個方法,用於處理制定和刪除事件處理程序的操做:addEventListener() 和 removeEventListener()。全部DOM節點都包含者兩個方法,而且他們都接受三個參數:要處理的事件名、做爲事件處理程序的函數 和 一個布爾值。最後這個布爾值參數若是是true,表示在捕獲節點調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。
var div = document.getElementById("myDiv"); div.addEventListener("click", function(){ alert(this.id); }, false);
使用DOM2 級 方法添加事件處理程序的主要好處是能夠添加多個事件處理程序。
var div = document.getElementById("myDiv"); div.addEventListener("click", function(){ alert(this.id); }, false); div.addEventListener("click", function(){ alert("hello world"); }, false);
經過addEventListener() 方法添加的事件處理程序只能使用 removeEventListener() 來移除,移除時傳入的參數與添加處理程序使用的參數相同。這也意味着 經過 addEventListener() 添加的匿名函數將沒法移除。
var div = document.getElementById("myDiv"); var handler = function(){ alert(this.id); } div.addEventListener("click", handler, false); div.removeEventListener("click", handler, false);
大多數狀況下,都是將事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度地兼容各類瀏覽器。最好只在須要在事件到達目標以前截獲它的時候纔將事件處理程序添加到捕獲階段。若是不是特別須要,不建議在事件捕獲階段註冊事件處理程序。
4.IE 事件處理程序
IE實現了與DOM中相似的兩個方法:attachEvent() 和 detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。因爲IE8 及更造版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序都會被添加到冒泡階段。
div.attachEvent("onclick", function(){ alert("clicked"); });
在IE中使用attachEvent() 與使用DOM0 級方法的主要區別在於事件處理程序的做用域。在使用DOM0級方法的狀況下,事件處理程序會在其所屬元素做用域中運行;在使用attachEvent() 方法的狀況下,事件處理程序會在全局做用域中運行,所以this 等於 window。
一樣,經過attachEvent() 方法添加的匿名函數沒法經過detachEvent() 來移除,detachEvent() 同attachEvent() 方法的參數是同樣的。
5.跨瀏覽器的事件處理程序
只要恰當的使用能力檢測,就能夠編寫本身的跨瀏覽器的方式處理事件。只須要關注冒泡階段。
第一個要建立的方法是addHandler(),它的職責是是狀況分別使用DOM0 級方法、DOM2級方法或者IE方法來添加事件。這個方法屬於一個名爲EventUtil 的對象。addHandler() 方法接收三個參數:要操做的元素、事件名稱 和 事件處理程序函數。
與 addHandler() 對應的方法是 removeHandler() ,它也接受相同的參數。這個方法的職責是移除以前添加的事件處理程序。
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; } } };
在觸發DOM上的某個事件時,會產生一個事件對象 event,這個對象中包含着全部與事件有關的信息。包括致使事件的元素、事件類型 以及 其餘與特定事件相關的信息。例如鼠標操做致使的事件對象中,會包含鼠標位置信息,而鍵盤操做致使的事件對象中,會包含與按下的鍵有關的信息。全部的瀏覽器都支持event對象,但支持的方式不一樣。
1.DOM中的事件對象
兼容DOM的瀏覽器會將一個 event 對象傳入到事件處理程序中。不管指定事件處理程序時使用什麼方法(DOM0級或DOM2級),都會傳入event對象。
event 對象包含與建立它的特定事件有關的屬性和方法。觸發的事件類型不同,可用的屬性和方法也不同。不過全部事件都會有下列成員。
屬性/方法 | 類型 | 讀/寫 | 說明 |
currentTarget | Element | 只讀 | 其事件處理程序當前正在處理事件的那個元素。 |
target | Element | 只讀 | 事件的目標。 |
type | String | 只讀 | 被觸發的事件類型。 |
bubbles | Boolean | 只讀 | 代表事件是否冒泡。 |
stopImmediatePropagation() | Function | 只讀 | 取消事件的進一步捕獲或冒泡,同時組織任何事件處理程序被調用(DOM3 級事件中新增) |
stopPropagation() | Function | 只讀 | 取消事件的進一步捕獲或冒泡。若是bubbles 爲 true,則可使用這個方法。 |
eventPhase | Integer | 只讀 | 調用事件處理程序的階段:1表示捕獲階段,2表示「處於目標」,3表示冒泡階段。 |
cancelable | Boolean | 只讀 | 代表是否能夠取消事件的默認行爲。 |
preventDefault() | Function | 只讀 | 取消事件的默認行爲。若是cancelable 是 true,則可使用這個方法。 |
defaultPrevented | Boolean | 只讀 | 爲true 表示已經調用了 preventDefault() (DOM3 級事件中新增)。 |
detail | Integer | 只讀 | 與事件相關的細節信息。 |
trusted | Element | 只讀 | 爲true 表示見見是瀏覽器生成的。爲false 表示事件是由開發人員經過調用JavaScript建立的(DOM3級事件中新增)。 |
view | AbstractView | 只讀 | 與事件關聯的抽象視圖。等同於發生事件的 window 對象。 |
在事件處理程序內部,對象this 始終等於 currentTarget 的值,而 target 則只包含事件的實際目標。若是直接將事件處理程序指定給了目標元素,則this、currentTarget 和 target 包含相同的值。
var div = document.getElementById("myDiv"); div.onclick = function(event){ alert(event.currentTarget === this); //true alert(event.target === this); //true };
若是事件處理程序存在於節點的父節點中,那麼currentTarget、this 將表明父節點,而target 表示的是子節點。
var div = document.getElementById("myDiv"); document.body.onclick = function(event){ alert(event.currentTarget === this); //true alert(this === document.body); //true alert(event.target === div); //true };
要組織特定事件的默認行爲,可使用 preventDefault() 方法。例如,連接的默認行爲就是在被單擊時會導航到其 href 特性制定的URL。若是你想組織連接導航這一默認行爲,那麼經過連接的onclick 事件處理程序能夠取消它。
var link = document.getElementById("myLink"); link.onclick = function(event){ event.preventDefault(); };
使用 stopPropagation() 方法能夠當即中止事件在DOM層次中的傳播,即取消進一步的事件捕獲或者冒泡。例如,直接添加到一個節點的事件處理程序能夠調用 stopPropagation(),從而避免除法註冊在document.body 上面的事件處理程序。
var div = document.getElementById("myDiv"); document.body.onclick = function(event){ alert("hello"); //不會執行 }; div.onclick = function(event){ event.stopPropagation(); alert("propagation stoped"); };
只有在事件處理程序執行期間,event對象纔會存在,一旦事件處理程序執行完成,event對象就會被銷燬。
2.IE中的事件對象
與訪問DOM中的event 對象不一樣,要訪問IE中的event對象有集中不一樣的方式,取決於指定事件處理程序的方法。在使用DOM0 級方法添加事件處理程序時, event 對象做爲window 對象的一個屬性存在。
var div = document.getElementById("myDiv"); btn.onclick = function(){ var event = window.event; alert(event.type); //click };
若是事件處理程序是使用attachEvent() 添加的,那麼會有一個event 對象做爲參數被傳入事件處理程序函數中。
var div = document.getElementById("myDiv"); div.attachEvent("onclick", function(event){ alert(event.type); });
在像這樣使用attachEvent() 的狀況下,也能夠經過window 對象來訪問event 對象,就像使用DOM0 級方法時同樣。不過方便起見,同一個對象也會做爲參數傳遞。
IE 的 event 對象一樣也包含與建立它的事件相關的屬性和方法。
屬性/方法 | 類型 | 讀/寫 | 說明 |
cancelBubble | Boolean | 讀/寫 | 默認值爲false,但將其設置爲true就能夠取消事件冒泡(與DOM中的 stopPropagation() 方法的做用相同)。 |
returnValue | Boolean | 讀/寫 | 默認值爲true,但將其設置爲false 就能夠取消事件的默認行爲(與DOM中的preventDefault() 方法的做用相同)。 |
srcElement | Element | 只讀 | 事件的目標(與DOM 中的target 屬性相同)。 |
type | String | 只讀 | 被觸發的事件的類型。 |
由於事件處理程序的做用域是根據制定它的方式來肯定的,因此不能認爲this 會始終你等於事件目標,故而,最好仍是使用 event.srcElement 比較保險。
var div = document.getElementById("myDiv"); div.onclick = function(){ alert(window.event.srcElement === this); // true }; div.attachEvent("onclick", function(event){ alert(event.srcElement === this); //false });
3.跨瀏覽器的事件對象
繼續補充EventUtil對象,增長對event 對象的支持。
var EventUtil = { addHandler: function(element, type, handler){ //... }, removeHandler: function(element, type, handler){ //... }, getEvent: function(event){ return event ? event : window.event; }, getTarget: function(event){ return event.target || event.srcElement; }, preventDefault: function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation: function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } };
使用時須要在頁面中加入引入js文件的代碼,使用方法以下:
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> <script type="text/javascript" src="./eventutil.js"></script> <script type="text/javascript"> var div = document.getElementById("myDiv"); div.onclick = function(event){ event = EventUtil.getEvent(event); alert(event.type); //click alert(EventUtil.getTarget(event).id); //myDiv }; </script> </body> </html>
Web瀏覽器中可能發生的事件有不少類型。不一樣的事件類型具備不一樣的信息,而DOM3級事件規定了一下幾類事件。
除了這幾類事件以外,HTML5 也定義了一組事件,而有些瀏覽器還會在DOM 和BOM 中實現其餘專有事件。這些轉悠事件通常都是根據開發人員需求定製的,沒有什麼規範,所以不一樣瀏覽器實現有可能不一致。
1.UI事件
UI事件指的是那些不必定與用戶操做有關的事件。這些事件在DOM規範出現以前,都是以這種或那種形式存在的,而在DOM 規範中保留是爲了向後兼容。現有的UI 事件以下:
以上這些事件在DOM2級事件中都被歸爲HTML 事件,要肯定瀏覽器是否支持DOM2級事件規定的HTML事件,可使用如下代碼:
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
要肯定瀏覽器是否支持DOM3級事件 定義的事件,可使用以下代碼:
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
1.1 load事件
當頁面徹底加載後(包括全部圖像、JavaScript文件、CSS文件等外部資源),就會觸發window 上面的load事件。有兩種定義onload 事件處理程序的方式。第一種使用JavaScript 代碼:
EventUtil.addHandler(window, "load", function(event){ alert("Loaded!"); });
這裏的event對象不包含有管這個事件的任何附加信息,但在兼容DOM的瀏覽器中,event.target 屬性的值會被設置爲document,而IE並不會爲這個事件設置srcElement 屬性。
第二種制定onload事件處理程序的方式是爲body元素添加一個onload 特性,
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body onload="alert('Loaded!')"> <div id="myDiv">Click Me</div> </body> </html>
通常來講在window 上面發生的任何事件均可以在<body> 元素中經過相應的特性來指定,由於在HTML中沒法訪問window 元素。可是建議儘量的使用JavaScript 方式。
在建立新的<img>元素時,能夠爲其制定一個事件處理程序,以便圖像加載完畢後給出提示。此時最重要的是要在指定src屬性以前先指定事件:
EventUtil.addHandler(window, "load", function(event){ var image = document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); //file:///home/zzl/Study/nodejs/studyjs/snal.jpg }); document.body.appendChild(image); image.src = "snal.jpg"; });
新圖像元素不必定要從添加到文檔後纔開始下載,只要設置了sr屬性就會開始下載。
一樣的功能也能夠經過使用DOM0 級的Image對象實現。在DOM出現以前,開發人員常用Image 對象在客戶端預先加載圖像。能夠像使用<img>元素同樣使用Image對象,只不過沒法將其添加到DOM樹中。
EventUtil.addHandler(window, "load", function(event){ var image = new Image(); //document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); //file:///home/zzl/Study/nodejs/studyjs/snal.jpg }); document.body.appendChild(image); image.src = "snal.jpg"; });
有的瀏覽器將Image對象實現爲<img>元素,但並不是全部瀏覽器都如此,因此最好將他們區別對待。
1.2 unload 事件
這個事件在文檔被徹底卸載後觸發。只要用戶從一個頁面切換到另外一個頁面,就會觸發unload 事件。而利用這個事件最多的狀況是清除引用,以免內存泄露。
同load事件相似,也有兩種制定onunload 事件處理程序的方式。第一種是使用JavaScript:
EventUtil.addHandler(window, "unload", function(event){ alert("Unloaded!"); });
具體來講,當發生如下狀況時,會發出 unload 事件:
可是實際上,這行代碼並無執行,查了不少資料發現,由於onunload函數是在body已經關閉後才調動的,因此若是使用alert的話(alert父窗口是body)就會報錯或不顯示!
第二種方法仍然是在<body>元素添加一個特性(與load事件相似)。
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body onunload="alert('Unloaded!')"> <div id="myDiv">Click Me</div> </body> </html>
1.3. resize事件
當瀏覽器窗口被調整到一個新的高度或寬度時,就會觸發resize 事件。這個事件是在window 窗口上觸發。
關於什麼時候會觸發resize 事件,不一樣瀏覽器有不一樣的機制,IE、Safari、Chrome和Opera 會在瀏覽器窗口變化了1像素時就觸發resize事件,而後隨着變化不斷重複觸發。Firefox則最實惠在用戶中止調整窗口大小是纔會觸發resize事件。因此應該注意不要在這個事件中添加大計算量的代碼,由於這些代碼有可能被頻繁執行,從而致使瀏覽器反應明顯變慢。
2.焦點事件
焦點事件會在頁面元素得到或時區焦點是觸發。利用這個事件並與 document.hasFocus() 方法及 document.activeElement 屬性配合,能夠直銷用戶在頁面上的行蹤。
3.鼠標與滾輪事件
鼠標事件是Web開發中最經常使用的一類事件,畢竟鼠標仍是最主要的定位設備。DOM3 級事件中定義了9個鼠標事件:
只有在同一個元素上相機觸發mousedown 和 mouseup 事件,纔會觸發 click 事件;若是mousedown 或 mouseup 中的一個被取消,就不會觸發click 事件。相似的,只有觸發兩次click 事件,纔會觸發一次 dblclick 事件,若是有代碼阻止了連續兩次觸發click,那麼就不會觸發dblclick 事件了。這四個事件觸發的順序始終以下:
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> <div id="consol"></div> <script type="text/javascript" src="./eventutil.js"></script> <script type="text/javascript"> var click = document.getElementById("myDiv"); var info = document.getElementById("consol"); EventUtil.addHandler(click, "click", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("clicked")); info.appendChild(p); }); EventUtil.addHandler(click, "mousedown", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("mousedowned")); info.appendChild(p); }); EventUtil.addHandler(click, "mouseup", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("mouseuped")); info.appendChild(p); }); EventUtil.addHandler(click, "dblclick", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("dblclicked")); info.appendChild(p); }); </script> </body> </html>
雙擊後輸出樣式以下:
鼠標事件中還有一類滾輪事件,其實就是一個mousewheel 事件。稍後介紹。
3.1. 客戶區座標位置
鼠標事件都是在瀏覽器視口中特定位置上發生的。這個位置信息保存在事件對象的clientX 和clientY 屬性中。全部瀏覽器都支持這兩個屬性,他們的值表示事件發生時,書白哦指針在視口中的水平和垂直座標。
EventUtil.addHandler(click, "click", function(event){ var p = document.createElement("p"); event = EventUtil.getEvent(event); p.appendChild(document.createTextNode("Client coordinates: " + event.clientX + ", " + event.clientY)); info.appendChild(p); });
3.2. 頁面座標位置
頁面座標位置表示的座標是從頁面自己而非視口左邊和頂邊計算的,用pageX 和 pageY 表示。
EventUtil.addHandler(click, "click", function(event){ var p1 = document.createElement("p"); event = EventUtil.getEvent(event); p1.appendChild(document.createTextNode("Client coordinates: " + event.clientX + ", " + event.clientY)); var p2 = document.createElement("p"); p2.appendChild(document.createTextNode("Page coordinates: " + event.pageX + ", " + event.pageY)); info.appendChild(p1); info.appendChild(p2); });
在頁面沒有滾動的狀況下,pageX 和pageY 的值與 clientX 和 clientY 的值相等。
3.3. 屏幕座標位置
這個位置是相對於整個電腦屏幕的位置。經過screenX 和 screenY 屬性表示。
3.4.修改鍵
雖然鼠標事件主要是使用鼠標來觸發的,但在按下鼠標時鍵盤上的某些鍵的狀態也能夠影響到所要採起的操做。這些修改鍵就是 Shift、Ctrl、Alt、Meta(在windows鍵盤中是windows鍵,在蘋果機中是 Cmd鍵),他們常常被用來修改鼠標事件的行爲。DOM爲此規定了四個屬性,表示這些修改鍵的狀態:shiftKey、ctrlKey、altKey 和 metaKey。這些屬性包含的都是布爾值,true表示相應的鍵被按下,反之則爲false。
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "click", function(event){ event = EventUtil.getEvent(event); var keys = new Array(); if(event.shiftKey){ keys.push("shift"); } if(event.ctrlKey){ keys.push("ctrl"); } if(event.altKey){ keys.push("alt"); } if(event.metaKey){ keys.push("meta"); } alert("keys: " + keys.join(", ")); });
3.5. 相關元素
在發生mouseover 和 mouseout 事件時,還會涉及更多的元素。這兩個事件都會涉及把鼠標指針從一個元素的邊界以內移動到另外一個元素的邊界以內。
對mouseover事件而言,事件的主要目標是得到光標的元素,而相關元素就是哪一個失去光標的元素。相似的,對mouseout事件而言,事件的主目標是失去光標的元素,而相關的元素則是得到光標的元素。
DOM經過event 對象的 relatedTarget 屬性提供了相關元素的信息。這個屬性只對於mouseover 和 mouseout 事件才包含值;對於其餘事件,這個屬性的值是null。 IE8 及以前的版本不支持relatedTarget屬性,但提供了保存着一樣信息的不一樣屬性。在mouseover事件中 fromElement 屬性中保存了相關元素;在mouseout 事件中 toElement 屬性中保存着相關元素。
添加到EventUtil 對象中,以實現跨瀏覽器取得相關元素:
var EventUtil = { //。。。。 getRelatedTarget: function(event){ if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else{ return null; } } };
使用方法以下:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "mouseover", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); var relatedTarget = EventUtil.getRelatedTarget(event); alert("Mouse move from " + relatedTarget.tagName + " to " + target.tagName); //Mouse move from HTML to DIV });
3.6. 鼠標按鈕
只有在主鼠標按鈕被單擊(或鍵盤迴車鍵被按下)纔會觸發click 事件,所以檢測按鈕的信息並沒必要要。但對於mousedown 和 mouseup 事件來講,則在其event 對象存在一個button 屬性,表示按下或釋放的按鈕。DOM的button 屬性可能有以下3個值:
IE8 及以前版本也提供了button 屬性,但這個屬性的值與DOM 的button 屬性截然不同。
因爲單獨使用能力檢測沒法肯定差別(兩種模型有同名的button 屬性),所以能夠經過檢測瀏覽器是否支持DOM版鼠標事件來判斷是否IE瀏覽器。
var EventUtil = { //。。。 getButton: function(event){ if(document.implementation.hasFeature("MouseEvents", "2.0")){ return event.button; }else{ switch(event.button){ case 0: case 1: case 3: case 5: case 7: return 0; case 2: case 6: return 2; case 4: return 1; } } } };
3.7 觸摸設備
iOS 和 Android 設備的實現很是特別,由於這些設備沒有鼠標。在面向iPhone和 iPad 中的Safari 開發是,要記住:
4.鍵盤與文本事件
有3個鍵盤事件:
雖然全部的元素都支持以上3個事件,可是隻有在用戶經過文本框輸入文本時才最經常使用到。
只有一個文本事件:textInput。這個事件是對keypress 的補充,用意是將文本顯示給用戶以前更容易攔截文本。在文本插入文本框以前會觸發textInput 事件。
在用戶按了一下鍵盤上的字符鍵時,首先會觸發keydown 事件,而後緊跟着是 keypress 事件,最後會觸發keyup 事件。其中keydown和keypress 都是在文本框發生變化以前被觸發的;而keyup事件則是在文本框已經發生變化以後被觸發的。
若是用戶按下的是一個非字符鍵,那麼首先會觸發keydown 事件,而後就是keyup 事件。
鍵盤事件與鼠標事件同樣,都支持相同的修改鍵。並且鍵盤事件的事件對象中也有 shiftKey、ctrlKey、altKey 和 metaKey 屬性。
4.1 鍵碼
在發生keydown 和 keyup 事件時, event對象的 keyCode 屬性中會包含一個代碼,與鍵盤上一個特定的鍵對應。對數字字母字符鍵,keyCode 屬性的值與 ASCII 碼中對應小寫字母或數字的編碼相同。所以數字鍵7的keyCode 值是 55, 而字母 A鍵的keyCode 值爲 65 ----與Shift鍵的狀態無關。
4.2 字符編碼
發生keypress 事件意味着按下的鍵會影響到屏幕中文本的顯示。在全部的瀏覽器中,按下可以插入或刪除的字符的鍵都會觸發keypress 事件;按下其餘鍵可否觸發keypress 事件因瀏覽器而異。
event 對象還支持一個 charCode 屬性,這個屬性只有在發生keypress 事件時才包含值,並且這個值是按下的那個鍵所表明字符的ASCII碼。此時的keyCode一般等於0或者也可能等於所按鍵的鍵碼。IE8 及以前的版本和Opera 則是在keyCode 中保存字符的ASCII編碼。想要以跨瀏覽器的方式取得字符編碼,必須首先檢測 charCode屬性是否可用,若是不可用則使用keyCode。
var EventUtil = { //。。。。 getCharCode: function(event){ if(typeof event.charCode == "number"){ return event.charCode; }else{ return event.keyCode; } } };
獲取案件的ASCII碼以後,經過String.fromCharCode() 方法就能夠轉換成實際的字符。
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "keypress", function(event){ event = EventUtil.getEvent(event); alert(String.fromCharCode(EventUtil.getCharCode(event))); });
4.3 DOM3級變化
儘管全部瀏覽器都實現了某種形式的鍵盤事件,DOM3級事件仍是作出了一些改變。好比DOM3級事件中的鍵盤事件,不在包含charCode屬性,而是包含兩個新屬性:key 和 char。
其中,key 屬性是爲了取代 keyCode 而新增的,它的值是一個字符串。在按下某個字符鍵時,key 的值就是相應的文本字符;在按下非字符鍵時,key 的值是相應鍵的名(如「Shift」或「Down」)。而char屬性在按下字符鍵時的行爲與key相同,但在按下非字符鍵時值爲null。
IE9支持key 屬性,但不支持char 屬性。Safari5 和 Chrome 支持名爲 keyIdentifier的屬性,在按下非字符鍵(如shift)的狀況下與key的值相同。對於字符鍵,keyIdentifier 返回一個格式相似「U+0000」的字符串,表示Unicode 值。
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "keypress", function(event){ event = EventUtil.getEvent(event); var identifier = event.key || event.keyIdentifier; if(identifier){ alert(identifier); } });
因爲存在跨瀏覽器的問題,所以不推薦使用key、keyIdentifier 或者 char。
DOM3級事件還添加了一個名爲location的屬性,這是一個數值,表示按下了什麼位置上的鍵:0表示默認鍵盤,1表示左側位置(例如左側的Alt鍵),2表示右側位置(例如右側的Shift鍵),3表示數字小鍵盤,4表示移動設備鍵盤(也就是虛擬鍵盤),5表示手柄(如任天堂wii控制器)。IE9支持這個屬性。Safari 和Chrome 支持名爲keyLocation的等價屬性,但有Bug----值始終是0,除非按下了數字鍵盤返回3,否子不會是1\2\4\5
同key屬性同樣,支持location 的瀏覽器也很少,因此在跨瀏覽器開發中不推薦使用。
4.4. textInput 事件
DOM3級事件規範中引入了一個新事件,名叫textInput。根據規範,當用戶在可編輯區域中輸入字符時,會觸發這個事件。這個用於替代keypress的textInput 事件的行爲稍有不一樣,區別之一就是任何能夠得到焦點的元素均可以觸發keypress 事件,但只有可編輯區才能觸發textInput 事件。區別之二是 textInput 事件只會在用戶按下可以輸入實際字符的鍵時才被觸發,而keypress 事件則在按下那些可以影響文本顯示的鍵是也會觸發(例如退格鍵)。
因爲textInput 事件主要考慮的是字符,所以它的event對象中還包括一個data屬性,這個屬性的值就是用戶輸入的字符(而非字符編碼)。例如:
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "textInput", function(event){ event = EventUtil.getEvent(event); alert(event.data); });
5.變更事件
DOM2級的變更事件能在DOM中的某一部分發生變化是給出提示。變更事件是給XML或 HTML DOM 設計的,兵不特定於某種語言。DOM2 級定義了以下變更事件:
使用下面的代碼能夠檢測出瀏覽器是否支持變更事件:
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");
IE8 及更早版本不支持任何變更事件。
6.HTML5事件
HTML5 詳盡列出了瀏覽器應該支持的全部事件。其中部分已經獲得完善支持。
6.1. contextmenu 事件
爲了實現上下文菜單,開發人員面臨的一個主要問題是如何肯定應該顯示上下文菜單(在windows中是右鍵單擊,在Mac中是Ctrl + 單擊),以及如何屏蔽與該操做關聯的默認上下文菜單。爲了解決這個問題,,就出現了contextmenu 這個事件,用於表示合適應該顯示上下文菜單,以便開發人員取消默認的上下文菜單而提供自定義的菜單。
因爲contextmenu 事件是冒泡的,所以能夠爲document 指定一個事件處理程序,用於處理頁面中發生的全部此類事件。這個事件的目標是發生用戶操做的元素。在全部瀏覽器中均可以取消這個事件:在兼容DOM 的瀏覽器中,使用 event.preventDefault(); 在IE 中,將event.returnValue 設爲false。
由於contextmenu 事件 屬於鼠標事件,因此其事件對象中包含與光標位置有關的全部屬性。一般使用contextmenu 事件來顯示自定義的上下文菜單,是使用onclick 事件處理程序來隱藏該菜單。co
<!DOCTYPE html> <html> <head> <title>Right Click </title> </head> <body> <div id="myDiv">right click</div> <ul id="myMenu" style="position:absolute;visibility:hidden;background-color:silver"> <li><a href="http://www.baidu.com">baidu</a></li> <li><a href="http://www.yahoo.com.cn">yahoo</a></li> <li><a href="http://www.163.com">163</a></li> </ul> <script type="text/javascript" src="eventutil.js"></script> <script type="text/javascript"> EventUtil.addHandler(window, "load", function(event){ var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "contextmenu", function(event){ event = EventUtil.getEvent(event); EventUtil.preventDefault(event); var menu = document.getElementById("myMenu"); menu.style.left = event.clientX +"px"; menu.style.top = event.clientY + "px"; menu.style.visibility = "visible"; }); EventUtil.addHandler(document, "click", function(event){ document.getElementById("myMenu").style.visibility = "hidden"; }); }); </script> </body> </html>
支持contextmenu 事件的瀏覽器有IE、Firefox、Safari、Chrome 和 Opera 11+.
6.2beforeunload 事件
之因此有發生在window對象上的beforeunload 事件,是爲了讓開發人員有可能在頁面卸載前阻止這一操做。這個事件會在瀏覽器卸載頁面以前觸發,能夠經過它來取消卸載並繼續使用原有頁面。可是不恩可以完全取消這個事件,由於那就至關於讓用戶沒法離開當前頁面了。
對於IE 及 Firefox 而言,經過將event.returnValue 設置爲要顯示給用戶的字符串能夠返回確認關閉對話框;對於Safari 和 Chrome 而言,須要將處理函數返回該字符串
EventUtil.addHandler(window, "beforeunload", function(event){ event = EventUtil.getEvent(event); var message = "I'm really going to miss you if you go."; event.returnValue = message; //for IE & Firefox return message; //for Safari & Chrome });
Chrome 彈出對話框以下:
6.3. DOMContentLoaded 事件
window 的load 事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會由於要加載的外部資源過多而頗費周折。而DOMContextLoaded 事件在造成完整的 DOM樹以後就會觸發,不理會圖像、JavaScript 文件、CSS 文件或其餘資源是否已經下載完畢。與load 事件不一樣,DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也意味着用戶能夠儘早地與頁面進行交互。
處理DOMContentLoaded 事件,能夠爲document 或 window 添加相應是事件處理程序,其event不會提供額外的信息,target屬性是document。 這個事件始終都會在load 事件以前觸發。
EventUtil.addHandler(document, "DOMContentLoaded", function(event){ alert("Content loaded"); });
IE9+、FireFox、Chrome、Safari 3.1+ 和 Opera 9+ 都支持DOMContentLoaded 事件。
6.4. readystatechange 事件
readystatechange 事件的目的是提供與文檔或元素加載狀態有關的信息,支持此事件的每一個對象都有一個 readyState 屬性,可能包含下列5個值中的一個:
6.5. pageshow 和 pagehide 事件
Firefox 和 Opera 有一個特性,名叫「往返緩存」(back-forward cache 或 bfcache),能夠在用戶使用瀏覽器的「後退」和「前進」按鈕是加快頁面的轉換速度。這個緩存中不只保存者頁面數據,還保存了DOM 和 JavaScript 的狀態;其實是將整個頁面都保存在了內存裏。若是頁面位於bfcache裏,那麼再次打開該頁面就不會觸發load事件。爲了更形象地說明bfcache 的行爲, Firefox 仍是提供了一些新事件。
第一個事件就是pageshow,這個事件在頁面顯示時觸發,不管該頁面是否來自bfcache。在從新加載的頁面中,pageshow 會在load 事件觸發後觸發;而對於bfcache 中的頁面,pageshow 會在頁面狀態徹底回覆的那一刻觸發。雖然這個事件的目標是document, 但必須將其事件處理程序添加到window。
第二個事件是pagehide 事件,該事件會在瀏覽器卸載頁面的時候觸發。
支持pageshow 和 pagehide 事件的瀏覽器有 FIrefox、Safari 5+、Chrome 和 Opera。
6.6. hashchange 事件
HTML5 增長了 hashchange事件,以便在URL的參數列表(及URL中「#」後面的全部字符串)發生變化時通知開發人員。之因此新增這個事件,是由於在Ajax應用中,開發人員常常要利用URL參數列表來保存狀態或導航信息。
因爲事件處理程序能夠爲現代Web應用提供交互能力,所以不少開發人員會部分青紅皁白地向頁面中添加大量的處理程序。在JavaScript 中,添加到頁面上的事件處理程序數量將直接關係到頁面的總體運行性能。致使這一問題的緣由是多方面的。首先每一個函數都是對象,都會佔用內存;內存中對象越多,性能就越差。其次,必須事先指定全部事件處理程序而致使的DOM 訪問次數,會延遲整個頁面的交互就緒事件。事實上,從如何利用好事件處理程序的角度出發,仍是有一些方法可以提高性能的。
1.事件委託
對「事件處理程序過多」問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。例如,click 事件會一直冒泡到document 層次。也就是說,咱們能夠爲整個頁面制定一個onclick 事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。
<!DOCTYPE html> <html> <head> <title>title</title> </head> <body> <ul> <li id="goBaidu">baidu</li> <li id="changeTitle">title</li> <li id="sayHi">sayHi</li> </ul> <script type="text/javascript" src="eventutil.js"></script> <script type="text/javascript"> var goBaidu = document.getElementById("goBaidu"); var changeTitle = document.getElementById("changeTitle"); var sayHi = document.getElementById("sayHi"); EventUtil.addHandler(goBaidu, "click", function(event){ location.href = "http://www.baidu.com"; }); EventUtil.addHandler(changeTitle, "click", function(event){ document.title = "I changed the title"; }); EventUtil.addHandler(sayHi, "click", function(event){ alert("hi"); }); </script> </body> </html>
若是在一個負載的Web 應用程序中,對全部可點擊的元素都採用這種方式,呢麼結果就會有數不清的代碼用戶添加事件處理程序。此時,能夠利用事件委託技術解決這個問題。使用事件委託,只須要在DOM樹中儘可能最高的層次上添加一個事件處理程序。
var list = document.getElementById("myLink"); EventUtil.addHandler(list, "click", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch(target.id){ case "goBaidu": location.href = "http://www.baidu.com"; break; case "changeTitle": document.title = "I changed the title"; break; case "sayHi": alert("hi"); break; } });
若是可行的話,也能夠考慮爲document對象添加一個事件處理程序,用以處理頁面上發生的某種特定類型的事件。這樣作與採起傳統的作法相比具備如下優勢:
最適合採用事件委託技術的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。雖然mouseover 和mouseout 事件也冒泡,但要適當處理他們並不容易,並且常常須要計算元素的位置。
2.移除事件處理程序
每當事件處理程序制定給元素時,運行中的瀏覽器代碼與支持頁面交互的JavaScript 代碼 之間就會創建一個鏈接。這種鏈接越多,頁面執行起來就越慢。如前所述能夠採用事件委託技術,限制創建的鏈接數量。另外在不須要的時候移除事件處理程序,也是解決這個問題的一種方案。內存中留有那些過期不用的「空事件處理程序」(dangling event handler),也是形成Web應用程序內存與性能問題的主要緣由。
通常來講,最好的作法是在頁面卸載以前,先經過onunload 事件處理程序移除全部事件處理程序。在此,事件委託技術再次表現出它的優點,須要跟蹤的事件處理程序越少,移除他們就越容易。對這種相似撤銷的操做,咱們能夠把它想象成:只要經過onload 事件處理程序 添加的東西,最後都要經過onunload 事件處理程序將它移除。
事件,就是網頁中某個特別值得關注的瞬間。事件常常有用戶操做或經過其餘瀏覽器功能來觸發。其實,也可使用JavaScript 在任意時刻觸發特定的事件,而此時的事件就如同瀏覽器建立的事件同樣。也就是說這些事件該冒泡還會冒泡,並且照樣可以致使瀏覽器執行已經制定的處理他們的事件處理程序。
1.DOM中的事件模擬
能夠造document對象上使用 createEvent() 方法建立event 對象。這個方法接收一個參數,即表示要建立的事件類型的字符串。在DOM2級中,全部的這些字符串都是使用英文複數形式,而在DOM3級中都變成了單數。這個字符串能夠是下列幾個字符串之一
在建立了event 對象以後,還須要使用與事件有關的信息對其進行初始化,每種類型的event對象都有一個特殊的方法,爲他傳入適當的數據u,就能夠初始化該event 對象。不一樣類型的這個方法的名字也不相同,具體要取決與 createEvent() 中使用的參數。
模擬事件的最後一部就是觸發事件。這一步須要使用dispatchEvent() 方法,全部支持事件的DOM節點都支持這個方法。調用dispatchEvent() 方法時,須要傳入一個參數,即表示要觸發事件的event對象。觸發事件後,改時間就躋身「官方事件」之列了,於是可以照樣冒泡並引起相應事件處理程序的執行。
1.1.模擬鼠標事件
建立新的鼠標事件對象併爲其指定必要的信息,就能夠模擬鼠標事件。建立鼠標事件對象的方法是爲createEvent() 傳入字符串MouseEvents。返回的對象有一個名爲 initMouseEvent() 方法,用於制定與該鼠標事件有關的信息,這個方法接收15個參數,分別與鼠標事件中每一個典型的屬性一一對應:
其中前四個參數對正確觸發事件相當重要,由於瀏覽器要用到這些參數。
var sayHi = document.getElementById("sayHi"); var event = document.createEvent("MouseEvents"); event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); sayHi.dispatchEvent(event);
事件是將JavaScript 與網頁聯繫在一塊兒的主要方式。DOM3級事件規範和HTML5 定義了常見的大多數事件。即便有規範定義了基本事件,但不少瀏覽器仍然在規範以外實現了本身的轉悠事件,從而爲開發人員提供更多掌握用戶交互的手段。
在使用事件時,須要考慮以下一些內存與性能方面的問題。
事件是JavaScript 中最重要的主題之一,深刻理解事件的工做機制以及他們對性能的影響相當重要。