標籤:
如今的JavaScript技術界裡最火熱的一項技術應該是‘事件委託(event delegation)’了。使用事件委託技術能讓你避免對特定的每個節點添加事件監聽器;相反,事件監聽器是被添加到它們的父元素上。事件監聽器會分析從子項目冒泡上來的事件,找到是哪個子項目的事件。基本概念非常簡單,但仍有很多人不理解事件委託的工作原理。這裡我將要解釋事件委託是如何工作的,並提供幾個純JavaScript的基本事件委託的例子。
假定我們有一個UL元素,它有幾個子項目:
<ul id="parent-list"> <li id="post-1">Item 1</li> <li id="post-2">Item 2</li> <li id="post-3">Item 3</li> <li id="post-4">Item 4</li> <li id="post-5">Item 5</li> <li id="post-6">Item 6</li></ul>
我們還假設,當每個子項目被點擊時,將會有各自不同的事件發生。你可以給每個獨立的li元素添加事件監聽器,但有時這些li元素可能會被刪除,可能會有新增,監聽它們的新增或刪除事件將會是一場噩夢,尤其是當你的監聽事件的代碼放在應用的另一個地方時。但是,如果你將監聽器安放到它們的父元素上呢?你如何能知道是那個子項目被點擊了?
簡單:當子項目的事件冒泡到父ul元素時,你可以檢查事件對象的target屬性,捕獲真正被點擊的節點元素的引用。下面是一段很簡單的JavaScript代碼,示範了事件委託的過程:
// 找到父元素,添加監聽器...document.getElementById("parent-list").addEventListener("click",function(e) { // e.target是被點擊的元素! // 如果被點擊的是li元素 if(e.target && e.target.nodeName == "LI") { // 找到目標,輸出ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); }});
第一步是給父元素添加事件監聽器。當有事件觸發監聽器時,檢查事件的來源,排除非li子項目事件。如果是一個li元素,我們就找到了目標!如果不是一個li元素,事件將被忽略。這個例子非常簡單,UL和li是標準的父子搭配。讓我們實驗一些差異比較大的元素搭配。假設我們有一個父元素div,裡面有很多子項目,但我們關心的是裡面的一個帶有”classA” CSS類的A標記:
// 獲得父元素DIV, 添加監聽器...document.getElementById("myDiv").addEventListener("click",function(e) { // e.target是被點擊的元素 if(e.target && e.target.nodeName == "A") { // 獲得CSS類名 var classes = e.target.className.split(" "); // 搜尋匹配! if(classes) { // For every CSS class the element has... for(var x = 0; x < classes.length; x++) { // If it has the CSS class we want... if(classes[x] == "classA") { // Bingo! console.log("Anchor element clicked!"); // Now do something here.... } } } }});
上面這個例子中不僅比較了標籤名,而且比較了CSS類名。雖然稍微複雜了一點,但還是很具代表性的。比如,如果某個A標記裡有一個span標記,則這個span將會成為target元素。這個時候,我們需要上溯DOM樹結構,找到裡面是否有一個 A.classA 的元素。
因為大部分程式員都會使用jQuery等工具庫來處理DOM元素和事件,我建議大家都使用裡面的事件委託方法,因為這裡工具庫裡都提供了進階的委託方法和元素甄別方法。
希望這篇文章能協助你理解JavaScript事件委託的幕後原理,希望你也感受到了事件委託的強大用處!
(英文:davidwalsh.)
傳統的事件處理
事件委託就是在一個頁面上使用一個事件來管理多種類型的事件。這並不是一個新的想法,但對於把握效能來說卻很重要。通常情況,你會在web應用程式中看到這樣的代碼:document.getElementById("help-btn").onclick = function(event){ openHelp(); };
document.getElementById("save-btn").onclick = function(event){ saveDocument(); };
document.getElementById("undo-btn").onclick = function(event){ undoChanges(); };
這種傳統的編碼方式給每個元素分配單獨的事件處理方法。對於互動少的網站來說,這樣做是可以的。然而,對於大型的wen應用程式,當存在大量的事件處理的時候,就會顯得反應遲鈍。這裡要關注的不是速度問題,而是記憶體佔用問題。如果有數百個互動,DOM元素和JavaScript代碼就會有數百個關聯。web應用需要佔用的記憶體越多,它的響應速度就越慢。事件委託能將這個問題減小。
事件冒泡及捕獲 要不是事件的下面這些屬性,事件委託將成為可能。早期的web開發,瀏覽器廠商很難回答一個哲學上的問題:當你在頁面上的一個地區點擊時,你真正感興趣的是哪個元素。這個問題帶來了互動的定義。在一個元素的界限內點擊,顯得有點含糊。畢竟,在一個元素上的點擊同時也發生在另一個元素的界限內。例如單擊一個按鈕。你實際上點擊了按鈕地區、body元素的地區以及html元素的地區。
伴隨著這個問題,兩種主流的瀏覽器Netscape和IE有不同的解決方案。Netscape定義了一種叫做事件捕獲的處理方法,事件首先發生在DOM樹的最高層對象(document)然後往最深層的元素傳播。在圖例中,事件捕獲首先發生在document上,然後是html元素,body元素,最後是button元素。
IE的處理方法正好相反。他們定義了一種叫事件冒泡的方法。事件冒泡認為事件促發的最深層元素首先接收事件。然後是它的父元素,依次向上,知道document對象最終接收到事件。儘管相對於html元素來說,document沒有獨立的視覺表現,他仍然是html元素的父元素並且事件能冒泡到document元素。所以圖例中噢噢那個button元素先接收事件,然後是body、html最後是document。 在定義DOM的時候,W3C顯然看到了這兩種方案各自的優點,所以DOM Level 2的事件規範中同時定義了這兩種方案。首先document元素獲得事件,然後捕獲階段向與事件最相關的元素傳播,當事件被此元素捕獲後,再冒泡到document元素。addEventListener()方法接受三個參數:事件名,事件處理函數,一個用於指定事件在捕獲階段處理還是在冒泡階段處理的布爾值。大部分的web開發人員都會使用false作為第三個參數這樣就跟IE中的attachEvent()一樣了。//bubbling phase handler
document.addEventListener("click", handleClick, false);
//capturing phase handler
document.addEventListener("click", handleClick, true);
通過冒泡實現事件委託事件委託的關鍵就是在通過冒泡方式實現在最高層(通常是document)處理事件。不是所有的事件都支援冒泡,但是滑鼠和鍵盤事件支援,並且這也是你所關心的。回顧下前面的例子,你可以通過在document上分配一個事件來處理所有的單擊事件,只需要通過區別節點來決定處理事件的方法。
document.onclick = function(event){
//IE doesn‘t pass in the event object
event = event || window.event;
//IE uses srcElement as the target
var target = event.target || event.srcElement;
switch(target.id){
case "help-btn":
openHelp();
break;
case "save-btn":
saveDocument();
break;
case "undo-btn":
undoChanges();
break;
//others?
}
};
使用事件委託,數個事件處理函數可以使用一個函數來管理。所有的單擊事件被委託給一個合適的函數來處理。同樣,mousedown, mouseup, mousemove, mouseover, mouseout, dblclick, keyup, keydown, 和keypress事件也可以這樣處理。但是,在事件委託中mouseover和mouseout的處理方法是不同的,當滑鼠從一個元素移動到它的子項目內的時候,被認為是"out"。
注意:你也可以使用事件捕獲來完成事件委託,但這隻能用在支援事件捕獲的非IE瀏覽器中。 優點事件委託對於web應用程式的效能有如下幾個優點:1.需要管理的函數變少了2.佔用的記憶體少了3.javascript代碼和Dom結構之間的關聯更少了4.在改變DOM結構中的innerHTML時,不需要改動事件處理函數 從傳統的事件處理方法轉向事件委託提高了大型web應用的效能。正因為它如此的重要,一些類似於YUI、jQuey的javascript庫也開始將事件委託應用在它們的核心介面中。實現事件委託是很輕鬆的,卻能帶來效能上很大的提高。尤其表現在你將數十個事件處理函數整合到一個函數裡。試一下事件委託,你就不會再使用傳統的事件處理方法了。
JavaScript事件委託的技術原理