JS 事件代理

事件處理器:onclick、onmouseover....html

在傳統的事件處理中,你須要爲每個元素添加或者是刪除事件處理器。然而,事件處理器將有可能致使內存泄露或者是性能降低——你用得越多這種風險就越大。node

JavaScript事件代理:當咱們須要對不少元素添加事件的時候,能夠經過將事件添加到它們的父節點而將事件委託給父節點來觸發處理函數。這主要得益於瀏覽器的事件冒泡機制web

它是怎麼運做的呢?

事件代理用到了兩個特性:事件冒泡以及目標元素編程

事件冒泡當一個元素上的事件被觸發的時候,好比說鼠標點擊了一個按鈕,一樣的事件將會在那個元素的全部祖先元素中被觸發。這一過程被稱爲事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。瀏覽器

目標元素:任何一個事件的目標元素都是最開始的那個元素也就是咱們出發的元素。在咱們的這個例子中也就是按鈕。ruby

使用事件代理:把事件處理器添加到一個元素上,等待一個事件從它的子級元素裏冒泡上來,而且能夠得知這個事件是從哪一個元素開始的。app

用代碼寫出來是什麼樣呢?

咱們所要關心的只是如何檢測目標元素而已。比方說咱們有一個table元素,ID是「report」,咱們爲這個表格添加一個事件處理器以調用editCell函數。editCell函數須要判斷傳到table來的事件的目標元素。考慮到咱們要寫的幾個函數中都有可能用到這一功能,因此咱們把獲取目標元素單獨放到一個名爲getEventTarget的函數中:框架

function getEventTarget(e) {
    //event屬性兼容寫法
    e = e || window.event;
    //獲取目標元素兼容寫法
    return e.target || e.srcElement;
}

 接下來就是editCell函數了,這個函數調用到了getEventTarget函數。一旦咱們獲得了目標元素,剩下的事情就是看看它是不是咱們所須要的那個元素了。ide

function editCell(e) {
    var target = getEventTarget(e);
    if(target.tagName.toLowerCase() == 'td') {
         // DO SOMETHING WITH THE CELL
    }
}

在editCell函數中,咱們經過檢查目標元素標籤名稱的方法來肯定它是不是一個表格的單元格。函數

事件冒泡及捕獲

以前的介紹中已經說到了瀏覽器的事件冒泡機制。這裏再詳細介紹一下瀏覽器處理DOM事件的過程。對於事件的捕獲和處理,不一樣的瀏覽器廠商有不一樣的處理機制,這裏咱們主要介紹W3C對DOM2.0定義的標準事件。

DOM2.0模型將事件處理流程分爲三個階段:1、事件捕獲階段,2、事件目標階段,3、事件起泡階段。如圖:

事件捕獲:當某個元素觸發某個事件(如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

相關文章
相關標籤/搜索