最近在深入實踐js中,遇到了一些問題,比如我需要為動態(tài)創(chuàng)建的DOM元素綁定事件,那么普通的事件綁定就不行了,于是通過上網(wǎng)查資料了解到事件委托,因此想總結(jié)一下js中的事件綁定與事件委托。
事件綁定
最直接的事件綁定:HTML事件處理程序
如下示例代碼,通過節(jié)點屬性顯式聲明,直接在HTML中,顯式地為按鈕綁定了click事件,當該按鈕有用戶點擊行為時,便會觸發(fā)myClickFunc方法。
/* html */
<button id="btn" onclick="myClickFunc()">
ClickMe
</button>
/* js */
// 事件處理程序
var myClickFunc = function(evt){
// TODO..
};
// 移除事件處理程序
myClickFunc = function(){};
顯而易見,這種綁定方式非常不友好,HTML代碼和JS代碼嚴重耦合在一起,比如當要修改一個函數(shù)名時候,就要修改兩次,
DOM 0 級事件處理程序
通過DOM操作動態(tài)綁定事件,是一種比較傳統(tǒng)的方式,把一個函數(shù)賦值給事件處理程序。這種方式也是應(yīng)用較多的方式,比較簡單??聪旅胬樱?/p>
/* html */
<button id="btn">ClickMe</button>
/* js */
// 事件處理程序
var myClickFunc = function(evt){
// TODO ...
};
// 直接給DOM節(jié)點的 onclick 方法賦值,注意這里接收的是一個function
document.getElementById('btn').onclick = myClickFunc;
// 移除事件處理程序
document.getElementById('btn').onclick = null;
DOM 2 級事件處理程序
通過事件監(jiān)聽的方式綁定事件,DOM2級事件定義了兩個方法,用于處理指定和刪除事件處理程序的操作。
// event: 事件名稱
// function: 事件函數(shù)
// boolean: false | true, true 為事件捕獲, false 為事件冒泡(默認);
Ele.addEventListener(event,function[,boolean]); // 添加句柄
ELe.removeEventListener(event,function[,boolean]); // 移除句柄
看個例子:
/* html */
<button id="btn">ClickMe</button>
/* js */
// 通過DOM操作進行動態(tài)綁定:
// 獲取btnHello節(jié)點
var oBtn = document.getElementById('btn');
// 增加第一個 click 事件監(jiān)聽處理程序
oBtn.addEventListener('click',function(evt){
// TODO sth 1...
});
// 增加第二個 click 事件監(jiān)聽處理程序
oBtn.addEventListener('click',function(evt){
// TODO sth 2...
});
// ps:通過這種形式,可以給btn按鈕綁定任意多個click監(jiān)聽;注意,執(zhí)行順序與添加順序相關(guān)。
// 移除事件處理程序
oBtn.removeEventListener('click',function(evt){..});
IE事件處理程序
DOM 2級事件處理程序在IE是行不通的,IE有自己的事件處理程序方法:attachEvent() 和detachEvent() 。這兩個方法的用法與addEventListener() 是一樣的,但是只接收兩個參數(shù),一個是事件名稱,另一個是事件處理程序的函數(shù)。為什么不使用第三個參數(shù)的原因呢?因為IE8以及更早的瀏覽器版本只支持事件冒泡??磦€例子:
/* html */
<button id="btn">ClickMe</button>
/* js */
var oBtn = document.getElementById('btn');
// 事件處理函數(shù)
function evtFn(){
console.log(this);
}
// 添加句柄
oBtn.attachEvent('onclick',evtFn);
// 移除句柄
oBtn.detachEvent('onclick',evtFn);
簡易的跨瀏覽器解決方法
如果我們既要支持IE的事件處理方法,又要支持 DOM 2級事件,那么就要封裝一個跨瀏覽器的事件處理函數(shù),如果支持 DOM 2級事件,就用addEventListener ,否則就用attachEvent 。例子如下:
//跨瀏覽器事件處理程序
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;
}
}
};
var oBtn = document.getElementById('btn');
function evtFn(){
alert('hello world');
}
eventUtil.addHandler(oBtn, 'click', evtFn);
eventUtil.removeHandler(oBtn, 'click', evtFn);
事件冒泡和事件捕獲
在了解事件委托之前,要先了解下事件冒泡和事件捕獲。
早期的web開發(fā),瀏覽器廠商很難回答一個哲學上的問題:當你在頁面上的一個區(qū)域點擊時,你真正感興趣的是哪個元素。這個問題帶來了交互的定義。在一個元素的界限內(nèi)點擊,顯得有點含糊。畢竟,在一個元素上的點擊同時也發(fā)生在另一個元素的界限內(nèi)。例如單擊一個按鈕。你實際上點擊了按鈕區(qū)域、body元素的區(qū)域以及html元素的區(qū)域。
伴隨著這個問題,兩種主流的瀏覽器Netscape和IE有不同的解決方案。Netscape定義了一種叫做事件捕獲的處理方法,事件首先發(fā)生在DOM樹的最高層對象(document)然后往最深層的元素傳播。在圖例中,事件捕獲首先發(fā)生在document上,然后是html元素,body元素,最后是button元素。
IE的處理方法正好相反。他們定義了一種叫事件冒泡的方法。事件冒泡認為事件促發(fā)的最深層元素首先接收事件。然后是它的父元素,依次向上,知道document對象最終接收到事件。盡管相對于html元素來說,document沒有獨立的視覺表現(xiàn),他仍然是html元素的父元素并且事件能冒泡到document元素。所以圖例中噢噢那個button元素先接收事件,然后是body、html最后是document。如下圖:
事件冒泡
簡單點說,事件冒泡就是事件觸發(fā)時,會從目標DOM元素向上傳播,直到文檔根節(jié)點,一般情況下,會是如下形式傳播:
targetDOM → parentNode → ... → body → document → window
如果希望一次事件觸發(fā)能在整個DOM樹上都得到響應(yīng),那么就需要用到事件冒泡的機制??聪旅媸纠?/p>
/* html */
<button id="btn">ClickMe</button>
/* js */
// 給按鈕增加click監(jiān)聽
document.getElementById('btn').addEventListener('click',function(evt){
alert('button clicked');
},false);
// 給body增加click監(jiān)聽
document.body.addEventListener('click',function(evt){
alert('body clicked');
},false);
在這種情況下,點擊按鈕“ClickMe”后,其自身的click事件會被觸發(fā),同時,該事件將會繼續(xù)向上傳播, 所有的祖先節(jié)點都將得到事件的觸發(fā)命令,并立即觸發(fā)自己的click事件;所以如上代碼,將會連續(xù)彈出兩個alert.
在有些時候,我們想讓事件獨立觸發(fā),所以我們必須阻止冒泡,用event 的stopPropagation() 方法。
<button id="btn">ClickMe</button>
/* js */
// 給按鈕增加click監(jiān)聽
document.getElementById('btn').addEventListener('click',function(evt){
alert('button clicked');
evt.stopPropagation(); //阻止事件冒泡
},false);
// 給body增加click監(jiān)聽
document.body.addEventListener('click',function(evt){
alert('body clicked');
},false);
此時,點擊按鈕后,只會觸發(fā)按鈕本身的click事件,得到一個alert效果;該按鈕的點擊事件,不會向上傳播,body節(jié)點就接收不到此次事件命令。
需要注意的是:
不是所有的事件都能冒泡,如:blur、focus、load、unload都不能
不同的瀏覽器,阻止冒泡的方式也不一樣,在w3c標準中,通過event.stopPropagation() 完成, 在IE中則是通過自身的event.cancelBubble=true 來完成。
事件委托
事件委托看起來挺難理解,但是舉個生活的例子。比如,有三個同事預(yù)計會在周一收到快遞。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞;二是委托給前臺MM代為簽收?,F(xiàn)實當中,我們大都采用委托的方案(公司也不會容忍那么多員工站在門口就為了等快遞)。前臺MM收到快遞后,她會判斷收件人是誰,然后按照收件人的要求簽收,甚至代為付款。這種方案還有一個優(yōu)勢,那就是即使公司里來了新員工(不管多少),前臺MM也會在收到寄給新員工的快遞后核實并代為簽收。舉個例子
HTML結(jié)構(gòu):
<ul id="ul-item">
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
</ul>
如果我們要點擊li標簽,彈出里面的內(nèi)容,我們就需要為每個li標簽綁定事件。
(function(){
var oUlItem = document.getElementById('ul-item');
var oLi = oUlItem.getElementsByTagName('li');
for(var i=0, l = oLi.length; i < l; i++){
oLi[i].addEventListener('click',show);
};
function show(e){
e = e || window.event;
alert(e.target.innerHTML);
};
})();
雖然這樣子能夠?qū)崿F(xiàn)我們想要的功能,但是如果這個UL中的LI子元素頻繁的添加或刪除,我們就需要在每次添加LI的時候為它綁定事件。這就添加了復雜度,并且造成內(nèi)存開銷較大。
更簡單的方法是利用事件委托,當事件被掏到更上層的父節(jié)點的時候,通過檢查事件的目標對象(target)來判斷并獲取事件源LI。
(function(){
var oUlItem = document.getElementById('ul-item');
oUlItem.addEventListener('click',show);
function show(e){
e = e || window.event;
var src = e.target;
if(src && src.nodeName.toLowerCase() === 'li'){
alert(src.innerHTML);
}
}
})();
這里我們?yōu)楦腹?jié)點UL添加了點擊事件,當點擊子節(jié)點LI標簽的時候,點擊事件會冒泡到父節(jié)點。父節(jié)點捕獲到事件之后,通過判斷e.target.nodeName 來判斷是否為我們需要處理的節(jié)點,并且通過e.target 拿到了被點擊的Li節(jié)點。從而可以獲取到相應(yīng)的信息,并做處理。
優(yōu)點:
通過上面的介紹,大家應(yīng)該能夠體會到使用事件委托對于web應(yīng)用程序帶來的幾個優(yōu)點:
管理的函數(shù)變少了。不需要為每個元素都添加監(jiān)聽函數(shù)。對于同一個父節(jié)點下面類似的子元素,可以通過委托給父元素的監(jiān)聽函數(shù)來處理事件。
可以方便地動態(tài)添加和修改元素,不需要因為元素的改動而修改事件綁定。
JavaScript和DOM節(jié)點之間的關(guān)聯(lián)變少了,這樣也就減少了因循環(huán)引用而帶來的內(nèi)存泄漏發(fā)生的概率。
參考資料
http://www./archives/71.html
http:///?p=15
|