在javasript中delegate這個詞經常出現,看字面的意思,代理、委托。那麼它究竟在什麼樣的情況下使用?它的原理又是什麼?在各種架構中,也經常能看到delegate相關的接口。這些接口又有什麼特殊的用法呢?這篇文章就主要介紹一下javascript
delegate的用法和原理,以及dojo,jquery等架構中delegate的接口。
javascript事件代理
首先介紹一下javascript的事件代理。事件代理在js世界中一個非常有用也很有趣的功能。當我們需要對很多元素添加事件的時候,可以通過将事件添加到它們的父節點而将事件委托給父節點來觸發處理函數。這主要得益于浏覽器的事件冒泡機制,後面會詳細介紹。下面我們具體舉個例子來解釋如何使用這個特性。這個例子主要取自david
walsh的相關文章(how javascript event delegation works)。
假設有一個 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>
當我們的滑鼠移到li上的時候,需要擷取此li的相關資訊并飄出懸浮窗以顯示詳細資訊,或者當某個li被點選的時候需要觸發相應的處理事件。我們通常的寫法,是為每個li都添加一些類似onmouseover或者onclick之類的事件監聽。
function addlisteners4li(linode){
linode.onclick = function clickhandler(){...};
linode.onmouseover = function mouseoverhandler(){...}
}
window.onload = function(){
var ulnode = document.getelementbyid("parent-list");
var linodes = ulnode.getelementbytagname("li");
for(var i=0, l = linodes.length; i < l; i++){
addlisteners4li(linodes[i]);
}
}
如果這個ul中的li子元素會頻繁地添加或者删除,我們就需要在每次添加li的時候都調用這個addlisteners4li方法來為每個li節點添加事件處理函數。這就添加的複雜度和出錯的可能性。
更簡單的方法是使用事件代理機制,當事件被抛到更上層的父節點的時候,我們通過檢查事件的目标對象(target)來判斷并擷取事件源li。下面的代碼可以完成我們想要的效果:
// 擷取父節點,并為它添加一個click事件
document.getelementbyid("parent-list").addeventlistener("click",function(e) {
// 檢查事件源e.targe是否為li
if(e.target && e.target.nodename.touppercase == "li") {
// 真正的處理過程在這裡
console.log("list item ",e.target.id.replace("post-")," was clicked!");
}
});
為父節點添加一個click事件,當子節點被點選的時候,click事件會從子節點開始向上冒泡。父節點捕獲到事件之後,通過判斷e.target.nodename來判斷是否為我們需要處理的節點。并且通過e.target拿到了被點選的li節點。進而可以擷取到相應的資訊,并作處理。
事件冒泡及捕獲
之前的介紹中已經說到了浏覽器的事件冒泡機制。這裡再詳細介紹一下浏覽器處理dom事件的過程。對于事件的捕獲和處理,不同的浏覽器廠商有不同的處理機制,這裡我們主要介紹w3c對dom2.0定義的标準事件。
dom2.0模型将事件處理流程分為三個階段:一、事件捕獲階段,二、事件目标階段,三、事件起泡階段。如圖:
事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,随着dom樹的節點向目标元素節點流去,直到到達事件真正發生的目标元素。在這個過程中,事件相應的監聽函數是不會被觸發的。
事件目标:當到達目标元素之後,執行目标元素該事件相應的處理函數。如果沒有綁定監聽函數,那就不執行。
事件起泡:從目标元素開始,往頂層元素傳播。途中如果有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。如果想阻止事件起泡,可以使用e.stoppropagation()(firefox)或者e.cancelbubble=true(ie)來組織事件的冒泡傳播。
jquery和dojo中delegate函數
下面看一下dojo和jquery中提供的事件代理接口的使用方法。
$("#link-list").delegate("a", "click", function(){
// "$(this)" is the node that was clicked
console.log("you clicked a link!",$(this));
jquery的delegate的方法需要三個參數,一個選擇器,一個時間名稱,和事件處理函數。
而dojo的與jquery相似,僅是兩者的程式設計風格上的差别:
require(["dojo/query","dojox/nodelist/delegate"], function(query,delegate){
query("#link-list").delegate("a","onclick",function(event) {
// "this.node" is the node that was clicked
console.log("you clicked a link!",this);
});
})
dojo的delegate子產品在dojox.nodelist中,提供的接口與jquery一樣,參數也相同。
優點通過上面的介紹,大家應該能夠體會到使用事件委托對于web應用程式帶來的幾個優點:
1.管理的函數變少了。不需要為每個元素都添加監聽函數。對于同一個父節點下面類似的子元素,可以通過委托給父元素的監聽函數來處理事件。
2.可以友善地動态添加和修改元素,不需要因為元素的改動而修改事件綁定。
3.javascript和dom節點之間的關聯變少了,這樣也就減少了因循環引用而帶來的記憶體洩漏發生的機率。
寫到這裡,突然想起了之前對于dojo
datagrid的困惑:那麼多的rows和cells,如何處理他們事件之間的關系。現在想想,使用委托就很簡單了。所有的事件委托到grid最外層的節點上,當事件發生的時候通過一些方法來擷取和添加事件的額外屬性,如rowindex,
cellindex,之後在配置設定到onrowclick,oncellclick之類的處理函數上。
在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");
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方法來制定執行作用域。
作者:puhongru
來源:51cto