事件處理器:onclick、onmouseover....
在傳統的事件處理中,你需要為每一個元素添加或者是刪除事件處理器。然而,事件處理器將有可能導致內存泄露或者是性能下降——你用得越多這種風險就越大。
JavaScript事件代理:當我們需要對很多元素添加事件的時候,可以通過將事件添加到它們的父節點而將事件委托給父節點來觸發處理函數。這主要得益於瀏覽器的事件冒泡機制
它是怎么運作的呢?
事件代理用到了兩個特性:事件冒泡以及目標元素。
事件冒泡:當一個元素上的事件被觸發的時候,比如說鼠標點擊了一個按鈕,同樣的事件將會在那個元素的所有祖先元素中被觸發。這一過程被稱為事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。
目標元素:任何一個事件的目標元素都是最開始的那個元素,也就是我們出發的元素。在我們的這個例子中也就是按鈕。
使用事件代理:把事件處理器添加到一個元素上,等待一個事件從它的子級元素里冒泡上來,並且可以得知這個事件是從哪個元素開始的。
用代碼寫出來是什么樣呢?
我們所要關心的只是如何檢測目標元素而已。比方說我們有一個table元素,ID是“report”,我們為這個表格添加一個事件處理器以調用editCell函數。editCell函數需要判斷傳到table來的事件的目標元素。考慮到我們要寫的幾個函數中都有可能用到這一功能,所以我們把獲取目標元素單獨放到一個名為getEventTarget的函數中:
function getEventTarget(e) {
//event屬性兼容寫法
e = e || window.event;
//獲取目標元素兼容寫法
return e.target || e.srcElement;
}
接下來就是editCell函數了,這個函數調用到了getEventTarget函數。一旦我們得到了目標元素,剩下的事情就是看看它是否是我們所需要的那個元素了。
function editCell(e) {
var target = getEventTarget(e);
if(target.tagName.toLowerCase() == 'td') {
// DO SOMETHING WITH THE CELL
}
}
在editCell函數中,我們通過檢查目標元素標簽名稱的方法來確定它是否是一個表格的單元格。
事件冒泡及捕獲
之前的介紹中已經說到了瀏覽器的事件冒泡機制。這里再詳細介紹一下瀏覽器處理DOM事件的過程。對於事件的捕獲和處理,不同的瀏覽器廠商有不同的處理機制,這里我們主要介紹W3C對DOM2.0定義的標准事件。
DOM2.0模型將事件處理流程分為三個階段:一、事件捕獲階段,二、事件目標階段,三、事件起泡階段。如圖:
事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨着DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程中,事件相應的監聽函數是不會被觸發的。
事件目標:當到達目標元素之后,執行目標元素該事件相應的處理函數。如果沒有綁定監聽函數,那就不執行。
事件起泡:從目標元素開始,往頂層元素傳播。途中如果有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來組織事件的冒泡傳播。
舉個栗子?
假設有一個 UL 的父節點,包含了很多個 Li 的子節點:
<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>
我們通常的做法是對每一個元素進行循環操作添加監聽事件:
var oUl=document.getElementById("parent-list");
var aLi=oUl.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick=function () {
console.log(this.id);
}
}
如果這個UL中的Li子元素會頻繁地添加或者刪除,我們就需要在每次添加Li添加事件處理函數,這就增加了復雜度和出錯的可能性。
更簡單的方法是使用事件代理機制,當事件被拋到更上層的父節點的時候,我們通過檢查事件的目標對象(target)來判斷並獲取事件源Li。下面的代碼可以完成我們想要的效果:
var oUl=document.getElementById("parent-list");
var aLi=oUl.getElementsByTagName('li');
function target(e) {
var oEvent=e||event;
return oEvent.target||oEvent.srcElement;
}
oUl.addEventListener('click',function (e) {
var oEvent=e||event;
var targets=target(oEvent);
if (targets.tagName.toLowerCase()=='li') {
console.log(targets.id);
}
})
為父節點添加一個click事件,當子節點被點擊的時候,click事件會從子節點開始向上冒泡。父節點捕獲到事件之后,通過判斷e.target.nodeName來判斷是否為我們需要處理的節點。並且通過e.target拿到了被點擊的Li節點。從而可以獲取到相應的信息,並作處理。
jQuery中delegate函數
下面看一下jQuery中提供的事件代理接口的使用方法。
$("#link-list").delegate("a", "click", function(){ // "$(this)" is the node that was clicked
console.log("you clicked a link!",$(this)); });
jQuery的delegate的方法需要三個參數,一個選擇器,一個事件名稱,和事件處理函數。
優點、缺點
通過上面的介紹,大家應該能夠體會到使用事件委托對於web應用程序帶來的幾個優點:
1.管理的函數變少了。不需要為每個元素都添加監聽函數。對於同一個父節點下面類似的子元素,可以通過委托給父元素的監聽函數來處理事件。
2.可以方便地動態添加和修改元素,不需要因為元素的改動而修改事件綁定。
3.JavaScript和DOM節點之間的關聯變少了,這樣也就減少了因循環引用而帶來的內存泄漏發生的概率。
潛在的問題也許並不那么明顯,但是一旦你注意到這些問題,你就可以輕松地避免它們:
你的事件管理代碼有成為性能瓶頸的風險,所以盡量使它能夠短小精悍。
不是所有的事件都能冒泡的。blur、focus、load和unload不能像其它事件一樣冒泡。事實上blur和focus可以用事件捕獲而非事件冒泡的方法獲得(在IE之外的其它瀏覽器中)。
在管理鼠標事件的時候有些需要注意的地方。如果你的代碼處理mousemove事件的話你遇上性能瓶頸的風險可就大了,因為mousemove事件觸發非常頻繁。而mouseout則因為其怪異的表現而變得很難用事件代理來管理。
在JavaScript編程中使用代理
上面介紹的是對DOM事件處理時,利用瀏覽器冒泡機制為DOM元素添加事件代理。其實在純JS編程中,我們也可以使用這樣的編程模式,來創建代理對象來操作目標對象。這里引用司徒正美相關文章中的一個例子:
var delegate = function(client, clientMethod) { return function() { return clientMethod.apply(client, arguments); } } var ClassA = function() { var _color = "red"; return { getColor: function() { console.log("Color: " + _color); }, setColor: function(color) { _color = color; } }; }; var a = new ClassA(); a.getColor(); a.setColor("green"); a.getColor(); console.log("執行代理!"); var d = delegate(a, a.setColor); d("blue"); console.log("執行完畢!"); a.getColor();
上面的例子中,通過調用delegate()函數創建的代理函數d來操作對a的修改。這種方式盡管是使用了apply(call也可以)來實現了調用對象的轉移,但是從編程模式上實現了對某些對象的隱藏,可以保護這些對象不被隨便訪問和修改。
在很多框架中都引用了委托這個概念用來指定方法的運行作用域。比較典型的如dojo.hitch(scope,method)和ExtJS的createDelegate(obj,args)。有興趣的同學可以看一下他們的源代碼,主要也是js函數的apply方法來制定執行作用域。(其實我對於上面這個栗子,不是特別理解是如何運用的事件代理,能夠深刻理解的童鞋請幫我留言講解一下,謝謝)
總結:
已經有一些使用主流類庫的事件代理示例出現了,比如說jQuery、Prototype以及Yahoo! UI。你也可以找到那些不用任何類庫的例子,比如說Usable Type blog上的這一個。一旦需要的話,事件代理將是你工具箱里的一件得心應手的工具,而且它很容易實現。
本博客是轉自兩篇文章,博主結合自己的理解,整理出這篇博文,原文鏈接如下:
http://blog.csdn.net/weinideai/archive/2009/01/19/3835839.aspx
http://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html