JS與HTML之間的交互經過事件實現。事件就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。可使用監聽器(或處理程序)來預約事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱爲觀察員模式,支持頁面的行爲與頁面的外觀之間的鬆散耦合。本文將介紹JS事件相關的基礎知識。
1、事件流
事件流描述的是從頁面中接受事件的順序。
事件冒泡
事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的結點(文檔)。如下面HTML頁面爲例,若是你點擊了頁面中的按鈕,那麼」click」事件會按照< button>、< body>、< html>、document的順序傳播。換句話說,事件冒泡指的就是事件從底層觸發事件的元素開始沿着DOM樹向上傳播,直到document對象。 javascript
<html> <head> <title>Test</title> </head> <body> <button id="myBtn">A Btn</button> </body> </html>
事件捕獲
與事件冒泡的思路相反,事件捕獲的思想是不太具體的節點應該更早地接收到事件,最具體的結點應該最後才接收事件。一樣仍是上面那個例子,點擊頁面中的按鈕以後,」click」事件會按照document、< html>、< body>、< button>的順序傳播。換句話說,事件捕獲就是指事件從document對象開始沿着DOM樹向下傳播,直到事件的實際目標元素。
DOM事件流
「DOM2級事件」規定的事件包括三個階段: 事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。而後是實際的目標接收到事件。最後一個階段是冒泡階段,能夠在這個階段對事件作出響應。
仍是以以前的點擊按鈕爲例,在DOM事件流中,捕獲階段,」click」事件從document開始向下傳遞到body元素(注意,實際目標button在捕獲階段不會接收到事件)。目標階段,button元素接收到」click」事件。最後,冒泡階段,事件又被傳播迴文檔。
2、事件處理程序
事件是用戶或瀏覽器自身執行的某種動做,而響應某個事件的函數就叫作事件處理程序或事件偵聽器。
HTML事件處理程序
這裏的HTML事件處理程序指的是直接在HTML元素裏面經過特性(attribute)定義的事件處理程序,請看下面的代碼示例。這樣是定的事件處理程序會建立一個封裝着元素屬性值的函數,this值等於事件的目標元素。經過這種方法指定事件處理程序存在很多缺點,不推薦使用。 html
<button onclick="alert('HaHa~')">Btn-1</button> <button onclick="alert('event.type')">Btn-2</button> <button onclick="handler()">Btn-3</button> <script type="text/javascript"> function handler() { alert("Haha~"); } </script>
DOM0級事件處理程序
經過JS指定事件處理程序的傳統方式就是將一個函數賦值給一個事件處理程序屬性,請看下面代碼示例。經過這種方式指定的事件處理程序是在元素的做用域中運行,this引用的是當前元素。這種方式添加的事件處理程序會在事件流的冒泡階段被處理。若要刪除事件,直接令onclick的值爲空便可。 java
var btn = document.getElementById("myBtn"); btn.onclick = function() { console.log("this.id"); // "myBtn" }; // 刪除事件處理程序 btn.onclick = null;
DOM2級事件處理程序
「DOM2級事件」定義了兩個方法用於指定和刪除事件處理程序,addEventListener()和removeEventListener()。全部DOM節點中都包含這兩個方法。這兩個方法都接收3個參數,要處理的事件、處理函數、布爾值。最後的布爾值爲true時表示在捕獲階段調用事件處理程序,爲false時表示在冒泡階段調用處理程序。與DOM0級方法同樣,這裏添加的事件處理程序也是在其依附的元素的做用域中運行。DOM2級方法添加事件處理程序的優點是能夠添加多個事件處理程序。這些事件處理程序會按照它們被添加的順序觸發。下面是代碼示例: 瀏覽器
var btn = document.getElementById("myBtn"); // 添加,觸發點擊事件時先輸出"myBtn"再輸出"HaHa~" btn.addEventListener("click", function() { console.log(this.id); }, false); btn.addEventListener("click", function() { console.log("HaHa~"); }, false);
經過addEventListener()添加的事件只能經過removeEventListener()來刪除。刪除時傳入的參數與添加時使用的參數應該保持一致。這也意味着經過addEventListener()添加的匿名函數將沒法刪除,由於沒法將添加時傳遞的匿名函數傳給removeEventListener(),即使在刪除的時候寫了一個如出一轍的函數,但此時這個函數只是一個新的匿名函數。請看下面代碼示例: 函數
var btn = document.getElementById("myBtn"); // 沒法刪除匿名函數 btn.addEventListener("click", function() { console.log(this.id); }, false); btn.removeEventListener("click", function() { console.log(this.id); }, false); // 正確的添加和刪除方式 function handler() { console.log(this.id); } btn.addEventListener("click", handler, false); btn.removeEventListener("click", handler, false);
大多數狀況下,都是將事件處理程序添加到事件流的冒泡階段,這樣能夠最大限度地兼容各類瀏覽器。最好只在須要在事件到達目標以前截獲它的時候纔將事件處理程序添加到捕獲階段。JS高級程序設計上給出的建議是,若是不是特別須要,不建議在事件捕獲階段註冊事件處理程序。
IE事件處理程序
IE實現了與DOM中相似的兩個方法: attachEvent()和deleteEvent()。這兩個方法接收兩個參數,事件處理程序名稱和事件處理程序。注意,第一個參數是事件處理程序名稱而不是事件名稱,也就是說在註冊點擊事件的處理程序時應該傳入」onclick」而不是」click」,這裏跟DOM的方法有些差異。另外,這兩個方法註冊的事件處理程序是在全局做用域中運行而不是元素做用域,this的值指向window。還有一點須要特別當心,經過attachEvent()方法也能夠添加多個事件處理程序,可是它們的執行順序卻不是按照它們被添加的順序,而是徹底相反,跟DOM方法大相徑庭。忽然以爲IE真的特別反人類~~~下面是代碼示例: this
var btn = document.getElementById("myBtn"); function handler1() { // ... } function handler2() { // ... } // 添加,觸發點擊事件時先執行handler2再執行handler1 btn.attachEvent("onclick", handler1); btn.attachEvent("onclick", handler2); // 刪除 btn.deleteEvent("onclick", handler1); btn.deleteEvent("onclick", handler2);
3、事件對象
在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含着全部與事件有關的信息,包括致使事件的元素、事件的類型以及其餘與特定事件相關的信息。
DOM中的事件對象
兼容DOM的瀏覽器會將一個event對象傳入事件處理程序中,不管指定事件處理程序時用的是DOM0仍是DOM2的方法,都會傳入event對象。event對象只有在事件處理程序執行期間纔會存在,一旦事件處理程序執行完畢,event對象就會被銷燬。下面是代碼示例: 設計
var btn = document.getElementById("myBtn"); btn.onclick = function(event) { console.log(event.type); // "click" } btn.addEventListener("click", function(event) { console.log(event.type); }, false);
event對象包含與建立它的特定事件有關的屬性和方法,觸發的事件類型不同,可用的屬性方法也有所不一樣。可是全部的事件都會有下列的屬性或方法:
bubbles: 布爾值,表示事件是否冒泡
cancelable: 布爾值,表示是否能夠取消事件的默認行爲
currentTarget: 元素,事件處理程序當前正在處理事件的那個元素
defaultPrevented: 布爾值,表示是否調用過preventDefault()方法
detail: 整數,與事件相關的細節信息
eventPhase: 整數,調用事件處理程序的階段,1表示捕獲階段,2表示目標階段,3表示冒泡階段
preventDefault(): 函數,取消事件的默認行爲,cancelable爲true時能夠調用該方法
stopImmediatePropagation(): 函數,取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用
stopPropagation(): 函數,取消事件的進一步捕獲或冒泡,bubbles爲true時能夠調用這個方法
target: 元素,事件的目標
trusted: 布爾值,爲true時表示事件是瀏覽器生成的,不然表示事件是經過JS建立的
type: 字符串,被觸發的事件類型
view: 與事件關聯的抽象視圖,等同於發生事件的window對象
下面代碼示例展現了上述部分屬性的用法,也能夠幫助咱們進一步理解事件流。假設頁面中有一個按鈕」myBtn」。當點擊按鈕時,this和currentTarget都等於body元素,由於事件處理程序是註冊在body元素上。target的值卻等於按鈕元素,由於它是click事件的真正目標。因爲按鈕上沒有註冊事件處理程序,結果」click」事件冒泡到了document.body那裏才獲得處理。 code
document.body.onclick = function(event) { console.log(event.currentTarget === document.body); // true console.log(this === document.body); // true console.log(event.target === document.getElementById("myBtn")); // true };
再看一個例子,下面代碼中,stopPropagation()方法取消了事件的進一步捕獲或冒泡。當我點擊按鈕時,原本應該會由於事件冒泡機制觸發按鈕和body元素上的點擊事件處理程序,輸出」From Bth …」和」From Body …」。如今點擊事件在按鈕元素上觸發以後就被阻止繼續在DOM層次中的傳播,所以body上的事件處理程序不會被觸發。 htm
var btn = document.getElementById("myBtn"); btn.onclick = function(event) { console.log("From Bth ..."); event.stopPropagation(); // 中止事件傳播 }; document.body.onclick = function() { console.log("From Body ..."); };
IE中的事件對象
在IE中,使用DOM0的方法添加事件處理程序時,event對象做爲window對象的一個屬性存在。若是是經過attachEvent()方法添加,則event對象是做爲參數傳入事件處理函數。下面是代碼示例: 對象
var btn = document.getElementById("myBtn"); btn.onclick = function() { var event = window.event; console.log(event.type); // "click" }; btn.attachEvent("onclick", function(event) { console.log(event.type); // "click" });
IE的event對象一樣也包含與建立它的事件相關的屬性和方法,這些屬性和方法也會由於事件類型的不一樣而有所差別。但全部事件對象都會包含下列屬性:
cancelBubble: 布爾值,可讀可寫,默認爲false。將其設置爲true時取消事件冒泡
returnValue: 布爾值,可讀可寫,默認爲true。將其設置爲false時取消事件的默認行爲
srcElment: 元素,事件的目標元素,與DOM中的target屬性相同
type: 字符串,事件類型
在IE中,事件處理程序的做用域是根據指定它的方式來肯定,this的值不必定是指向事件的目標元素。所以,使用srcElement屬性更具保險。請看下面代碼實例,第一種方式中this的值爲目標元素,而第二種方式,前面講過這種方式的事件處理程序是在全局做用域中執行,所以this的值爲window。
var btn = document.getElementById("myBtn"); btn.onclick = function() { console.log(window.event.srcElement === this); // true } btn.attachEvent("onclick", function(event) { console.log(event.srcElement === this); // false });