一文梳理JS事件

JavaScript與HTML的交互是通過事件進行的。事件,就是文檔或瀏覽器窗口發生的一些特定的交互瞬間。

  • 事件流
    • 事件捕獲
    • 事件冒泡
  • 事件處理程序
  • 事件委託

1. 事件流

如果單機頁面上的某個按鈕,認爲單擊事件不僅僅發生在按鈕上。火炬話說,在單擊按鈕的同時,你也單擊的按鈕的容器元素,甚至也單擊了整個頁面。

事件流描述的是從頁面中接收事件的順序。有意思的是,IE和Netscape開發團隊提出了幾乎完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕獲流。

1.1 事件冒泡

IE的事件流叫做事件冒泡,即時間開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,然後逐級向上傳播到較爲不具體的節點(文檔)。以下面的HTML頁面爲例:

<!DOCTYPE>
<html>
    <head>
        <title> test </title>
    </head>
    <body>
        <div id="myDiv">click me</div>
    </body>
</html>

在這裏,如果你單擊了頁面中的div元素,那麼click事件會按照如下順序傳播:

  1. div
  2. body
  3. html
  4. document

如圖所示:

所有的現代瀏覽器都支持事件冒泡,但在具體實現中略有差別.IE5.5及更早版本中事件冒泡會跳過<html>元素(從body直接跳到document)。IE9、Firefox、Chrome、和Safari則將事件一直冒泡到window對象。

1.2 事件捕獲

Netscape團隊提出的另一種事件流叫做事件捕獲。思想是:不大具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於事件到達預定目標之前捕獲它。仍以上面的栗子,單擊div元素就會以下列順序觸發click事件。

  1. document
  2. html
  3. body
  4. div

具體如圖所示:

事件捕獲流在IE9Safari,Chrome,OperaFireFox都得到支持,儘管DOM標準要求事件應該從document對象開始傳播,但這些瀏覽器都是從window對象開始捕獲事件的。由於老版本瀏覽器不支持,很少有人使用事件捕獲。建議使用事件冒泡

1.3 DOM事件流

“DOM2級事件”規定事件流包括三個階段:事件捕獲階段,處於目標階段和事件冒泡階段。首先發生的是事件捕獲,爲截獲事件提供了機會。然後是實際的目標接收到事件。最後一個階段是冒泡階段,可以在這個階段對事件做出響應。還是之前的栗子,如圖所示:

多數支持 DOM事件流的瀏覽器都實現了一種特定的行爲;即使“DOM2 級事件”規範明確要求捕
獲階段不會涉及事件目標,但 IE9SafariChromeFirefoxOpera 9.5 及更高版本都會在捕獲階段觸發事件對象上的事件。結果,就是有兩個機會在目標對象上面操作事件。

注意IE9SafariChromeFirefoxOpera 都支持 DOM 事件流;IE8 及更早版本不支持 DOM 事件流。

2. 事件處理程序

事件就是用戶或瀏覽器自身執行的某種動作。諸如 click 、 load 和 mouseover ,都是事件的名字。
而響應某個事件的函數就叫做事件處理程序(或事件偵聽器)。

2.1 HTML事件處理程序

某個元素支持的每種事件,都可以使用一個與相應事件處理程序同名的 HTML 特性來指定。這個
特性的值應該是能夠執行的 JavaScript 代碼。例如,要在按鈕被單擊時執行一些 JavaScript,可以像下面這樣編寫代碼:

<input type="button" value="Click Me" onclick="alert('Clicked')" />

當單擊這個按鈕時,就會顯示一個警告框。這個操作是通過指定 onclick 特性並將一些 JavaScript
代碼作爲它的值來定義的。

2.2 DOM 0級 事件處理程序

通過 JavaScript 指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性,爲所有現代瀏覽器所支持。

每個元素(包括 window 和 document )都有自己的事件處理程序屬性,這些屬性通常全部小寫,
例如 onclick 。將這種屬性的值設置爲一個函數,就可以指定事件處理程序,如下所示:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};

在此,我們通過文檔對象取得了一個按鈕的引用,然後爲它指定了 onclick 事件處理程序。但要
注意,在這些代碼運行以前不會指定事件處理程序,因此如果這些代碼在頁面中位於按鈕後面,就有可能在一段時間內怎麼單擊都沒有反應。

使用 DOM0 級方法指定的事件處理程序被認爲是元素的方法。因此,這時候的事件處理程序是在
元素的作用域中運行;換句話說,程序中的 this 引用當前元素。

2.3 DOM 2級事件處理程序

DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程序的操作:addEventListener() removeEventListener()所有 DOM 節點中都包含這兩個方法,並且它們都接受 3 個參數:要處理的事件名作爲事件處理程序的函數和一個布爾值。最後這個布爾值參數如果是 true ,表示在捕獲階段調用事件處理程序;如果是 false ,表示在冒泡階段調用事件處理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);

上面的代碼爲一個按鈕添加了 onclick 事件處理程序,而且該事件會在冒泡階段被觸發(因爲最
後一個參數是 false )。與DOM0級方法一樣,這裏添加的事件處理程序也是在其依附的元素的作用域中運行。使用 DOM2 級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。

通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移
除時傳入的參數與添加處理程序時使用的參數相同。這也意味着通過 addEventListener() 添加的匿名函數將無法移除

注意:IE9、Firefox、Safari、Chrome和 Opera 支持 DOM2 級事件處理程序。

2.4 IE事件處理程序

IE 實現了與 DOM 中類似的兩個方法: attachEvent()detachEvent() 。這兩個方法接受相同
的兩個參數:事件處理程序名稱與事件處理程序函數。由於IE8及更早版本只支持事件冒泡,所以通過attachEvent() 添加的事件處理程序都會被添加到冒泡階段。

要使用 attachEvent() 爲按鈕添加一個事件處理程序,可以使用以下代碼。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});

在 IE 中使用 attachEvent() 與使用 DOM0 級方法的主要區別在於事件處理程序的作用域。在使
DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用 attachEvent() 方法的情況下,事件處理程序會在全局作用域中運行,因此 this 等於 window 。

使用 attachEvent() 添加的事件可以通過 detachEvent() 來移除,條件是必須提供相同的參數。
與 DOM 方法一樣,這也意味着添加的匿名函數將不能被移除。不過,只要能夠將對相同函數的引用傳給 detachEvent() ,就可以移除相應的事件處理程序。

2.5 跨瀏覽器的事件處理程序

爲了以跨瀏覽器的方式處理事件,不少開發人員會使用能夠隔離瀏覽器差異的 JavaScript 庫,還有
一些開發人員會自己開發最合適的事件處理的方法。自己編寫代碼其實也不難,只要恰當地使用能力檢測即可。要保證處理事件的代碼能在大多數瀏覽器下一致地運行,只需關注冒泡階段。

第一個要創建的方法是 addHandler() ,它的職責是視情況分別使用 DOM0 級方法、DOM2 級方
法或 IE 方法來添加事件。這個方法屬於一個名叫 EventUtil 的對象,本書將使用這個對象來處理瀏覽器間的差異。 addHandler() 方法接受 3 個參數:要操作的元素、事件名稱和事件處理程序函數。
addHandler() 對應的方法是 removeHandler() ,它也接受相同的參數。這個方法的職責是移
除之前添加的事件處理程序——無論該事件處理程序是採取什麼方式添加到元素中的,如果其他方法無效,默認採用 DOM0 級方法。

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;
		}
	}
};

addHandler() removeHandler() 沒有考慮到所有的瀏覽器問題,例如在 IE 中的作用域問題。
不過,使用它們添加和移除事件處理程序還是足夠了。此外還要注意,DOM0 級對每個事件只支持一個事件處理程序。

3. 事件委託

3.1 什麼是事件委託?

事件委託,也稱事件代理(Event Delegation),是JS中常用綁定事件的技巧。事件委託就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。

3.2 爲什麼要使用事件委託?

衆所周知,瀏覽器的內存是有限的,如果很多的dom需要添加事件處理,直接給相應dom設事件處理程序的話,十分影響性能。

可以想象,如果有100個li,每個li都有相同的click事件,首先我們需要使用for循環訪問所有li元素,然後給他們添加事件,這樣,會使得訪問dom的次數增加,其次,每個li元素一個函數,佔用內存也會相當大。每一個函數都是對象,對象越多佔用內存越大,性能越差。同時,訪問dom次數的越多,引起瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是爲什麼性能優化的主要思想之一就是減少DOM操作的原因;如果要用事件委託,就會將所有的操作放到js程序裏面,與dom的操作就只需要交互一次,這樣就能大大的減少與dom的交互次數,提高性能。

3.3 事件委託原理

事件委託是利用事件的冒泡原理來實現,將事件加到 父元素 或 祖先元素上,觸發執行效果。

3.4 事件委託實現案例

如下所示,需要實現點擊li元素,彈出123:

<ul id="list">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

一般方法:

window.onload = function(){
    var list = document.getElementById("list");
    var lists = list.getElementsByTagName('li');
    for(var i=0;i<lists.length;i++){
        lists[i].onclick = function(){
            alert(123);
        }
    }
}

事件委託

window.onload = function(){
    var list = document.getElementById("list");
    list.onclick = function() {
        alert(123);
    }
}

5. 小結

事件是將Javascript與網頁聯繫在一起的主要方式。爲了將JS事件進行梳理,本文一一闡述了事件冒泡,事件捕獲,DOM事件流,循序漸進的將事件流這一脈絡梳理;然後,將事件處理程序包括HTML事件處理程序、DOM0級事件處理程序、IE事件處理程序、DOM2級事件處理程序等一一列舉,並給出了跨瀏覽器事件處理方案;最後,講述了事件委託這一JS中常用技巧,並列舉了使用這一技術的好處。希望在閱讀本文後,讀者對於JS事件有一個清晰的認知。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章