瀏覽器發展到第四代時(IE4及 Netscape Communicator 4),瀏覽器開發團隊遇到一個問題:頁面的哪一個部分會擁有某個特定的事件?能夠想象在一張紙上的一組同心圓,若是把手指放在圓心上,那麼你的手指指向的不是一個圓,而是紙上的全部圓。即在點擊一個按鈕時,不只點擊了按鈕,也點擊了整個頁面。html
事件流描述的是從頁面中接收事件的順序。不過IE 和 Netscape 開發團隊提出的想法差很少徹底相反。前端
IE的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕獲流。程序員
IE的事件流叫作事件冒泡(event bubbling),即事件開始時是由最具體的元素接收,而後逐級向上傳播到較爲不具體的節點。瀏覽器
備註:全部現代的瀏覽器都支持事件冒泡,可是在具體實現上還有一些差異。IE5.5 以及更早的版本中的事件冒泡會跳過 html 元素,即從body直接到 document。而IE九、Firefox、Chrome和Safari則將事件一直冒泡到window對象。
函數
Netscape Communicator 團隊提出的另外一種事件流叫作事件捕獲。思想是不太具體的節點應該更早的接收到事件,而最具體的節點應該最後接收到事件。this
事件捕獲的用意在於事件到達預約目標以前捕獲它。spa
雖然 事件捕獲 是Netscape Communicator 惟一支持的事件流模型,可是IE九、Firefox、Opera、Chrome和Safari目前也都支持這種事件流模型。code
儘管「DOM2級事件」規範要求事件應該從document對象開始傳播,可是這些瀏覽器都是從window對象開始捕獲事件的。orm
因爲老版本的瀏覽器不支持,所以不多有人使用事件捕獲。htm
「DOM2級事件」規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,這爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件做出響應。
以下:
<input type="button" value="click" onclick="alert('hello')" />
經過JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種爲事件處理程序賦值的方法是在第四代Web瀏覽器中出現的,並且至今仍然爲全部現代瀏覽器支持。一是簡單,二是具備跨瀏覽器優點。
每一個元素都有本身的事件處理程序屬性,這些屬性一般所有小寫,例如:onclick。將這種屬性的值設置爲一個函數,就能夠制定事件處理程序。
以下例:
var btn = document.getElementById("myBtn"); btn.onclick = function(){ alert(this.id); // "myBtn" } btn.onclick = null; // 刪除事件處理程序
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
「DOM2級事件」定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。都接受三個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。默認是 false。
使用DOM2級方法添加事件處理程序的主要好處是能夠添加多個事件處理程序。
var btn = document.getElementById("myBtn"); btn.addEventListener("click",function(){alert(this.id)}) // this 指的是元素自己 btn.addEventListener("click",function(){alert("hello word!"))})
經過 addEventListener 添加的事件,只能經過 removeEventListener 移除掉,移除時的參數須要和傳入的參數相同。這也意味着傳入的匿名參數沒法移除。所以儘可能避免使用匿名函數。
大多數狀況下,都是把事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度的兼容各類瀏覽器。萬不得已的時候,再添加到捕獲階段。
IE實現了與DOM中相似的兩個方法,attachEvent() 和 detachEvent()。這兩個方法接收兩個參數,由於只支持冒泡。
與addEventListener 和 removeEventListener 不一樣的是,接受的第一個參數,必須帶on。如單擊事件,爲"onclick";還有添加多個事件的時候,此方法按照添加的順序反向執行。
此方法與DOM0級方法的主要區別是在於事件處理程序的做用域。此方法的事件處理程序會在全局做用域中運行,其中的this爲window
由於不一樣瀏覽器對於事件的處理不同,因此能夠手寫一些事件兼容方法。以下:
var EventUtil = { // 添加事件處理程序 addHandler: function(element, type, handler) { if (element.addEventListener) { // DOM2級 事件處理程序,this 指向元素自己。按照添加的順序正向執行 element.addEventListener(type, handler, false); } else if (element.attachEvent) { // IE 事件處理程序,this 指向 window。按照添加的順序反向執行 element.attachEvent("on" + type, handler); } else { // DOM0級 事件處理程序。只能綁定一個事件處理程序 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; } }, // 獲取 event 對象。window.event 爲 IE 瀏覽器的獲取方式 getEvent: function(event) { return event ? event : window.event; }, // 獲取event的target。 event.srcElement 只對老版本的 IE 瀏覽器有效 getTarget: function(event) { return event.target || event.srcElement; }, // 取消事件的默認行爲 preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; // IE } }, // 阻止事件冒泡 stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; // IE } } }
備註:事件處理程序的做用域是根據指定他的方式肯定的,因此不能認爲this始終等於時間目標。仍是使用 srcElement 保險。
以下例:
<button id="myBtn">event</button> <script> var btn = document.getElementById('myBtn'); btn.onclick = function (){ alert(window.event.srcElement === this); // true } btn.attachEvent('onclick',function(event){ alert(event.srcElement === this) // false alert(this === window) // true }) </script>
最近在搞一個和前端程序員相關的公號,除了技術分享以外,也增長了對於職業發展、生活記錄之類的文章,歡迎你們關注,一塊兒聊天、吐槽,一塊兒努力工做,認真生活!