一、事件冒泡
1.1 事件的不同階段
Javascript事件在2個階段執行:捕獲與冒泡。
如下圖的Dom結構中如果指向錨點#1.1的鏈接被點擊,則依次會觸發document > body > ul > li > ul > li > a 的Click處理函數。至此完成捕獲階段。當這階段完成,開始冒泡階段,如圖中向上箭頭的順序。事件處理函數全部觸發。有興趣可以移步這裡,可以看到動態的過程。
我們對上述代碼稍加更改,假如alert,因為那個demo中的效果切換太快瞭,我們慢一點洗洗體會。【註:這裡訂閱的事件都是冒泡階段的,也是最常用的,因為IE並不支持訂閱捕獲階段的時間。比較特殊的還有Opera,常常遇到有些特性向Firefox系,偶爾會有個別特性像IE】。這篇文章也助於加深理解
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh" xml:lang="zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="developer" content="Realazy" />
<title>Bubble in JavaScript DOM</title>
<style type="text/css" media="screen">
p * {display:block; margin:4px; padding:4px; border:1px solid white;}
textarea {width:20em; height:2em;}
</style>
<script type="text/javascript">
//<![CDATA[
function init(){
var log = document.getElementsByTagName('textarea')[0];
var all = document.getElementsByTagName('p')[0].getElementsByTagName('*');
for (var i = 0, n = all.length; i < n; ++i){
all[i].onmouseover = function(e){
alert('鼠標現在進入的是:' + this.nodeName);
this.style.border = '1px solid red';
log.value = '鼠標現在進入的是:' + this.nodeName;
};
all[i].onmouseout = function(e){
alert('鼠標現在移出的是:' + this.nodeName);
this.style.border = '1px solid white';
};
}
var all2 = document.getElementsByTagName('p')[1].getElementsByTagName('*');
for (var i = 0, n = all2.length; i < n; ++i){
all2[i].onmouseover = function(e){
this.style.border = '1px solid red';
if (e) //停止事件冒泡
e.stopPropagation();
else
window.event.cancelBubble = true;
log.value = '鼠標現在進入的是:' + this.nodeName;
};
all2[i].onmouseout = function(e){
this.style.border = '1px solid white';
};
}
}
window.onload = init;
//]]>
</script>
</head>
<body>
<h1>Bubble in JavaScript DOM</h1>
<p>DOM樹的結構是:</p>
<pre><code>
UL
– LI
– A
– SPAN
</code></pre>
<p>
<ul>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
</ul>
</p>
<textarea></textarea>
<p>鼠標進入UL的任何一個子元素,如果不停止冒泡,我們從UL到SPAN都定義瞭鼠標懸停(<code>mouseover</code>)事件,這個事件會上升瞭UL,從而從鼠標所進入的元素到UL元素都會有紅色的邊。</p>
<p>
<ul>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
</ul>
</p>
<p>如果停止冒泡,事件不會上升,我們就可以獲取精確的鼠標進入元素。</p>
</body>
</html>
1.2 取消事件冒泡
其實1.1的代碼中已經包含瞭取消事件冒泡的代碼。這裡我們專門提出來寫,使其具有更好的兼容性與美觀。
1 function stopBubble(e) {2 if (e && e.stopPropagation) {3 e.stopPropagation(); //因為傳入瞭事件對象e,並且支持W3C標準的stopPropagation()4 } else {5 window.event.cancelBubble = true; //For IE6 }7 }
【註】:我們不能簡單的看到傳入瞭事件對象就判斷為非IE瀏覽器,因為有時候我們使用3.1的方式來綁定事件,此時極有可能也會傳入一個window.event的引用。
1.3 重載瀏覽器默認行為
對於a標簽等具有默認行為(如跳轉到某URL)的HTML元素,我們可能想要部分a表現的有特色些,點擊某a就是不跳轉,可以重載其默認行為。
function stopDetault(e) { if (e&&e.preventDetault) { e.preventDefault(); } else { window.event.returnValue = false;}return false;}使用方法:
document.getElementById("##").onclick = function (e) {//do sth.return stopDetault(e);}
我們也常用下面的方式阻止默認行為,所以阻止事件處理函數本身return false也就可以理解瞭。
<a href="javascript:alert('clicked');return false;">a link without redirect action</a>
《Pro Javascript Techniques》[美John Resig]一書中提到95%的情況中防止默認行為都有效,但是偶爾也會失效,因為該行為是由瀏覽器決定的,尤其是在文本域中防止敲擊和iframe內的行為。除此之外,都應該無大礙。這是一本學習javascript的好書。推薦。
二、 常見事件對象
2.1 this
this 關鍵字是javascript中提供對當前對象引用的變量。綁定事件時this通常指的是當前元素,但是也有例外!!初接觸javascript覺得這個this有點變換莫測,難以捉摸。
如果你的感覺也是這樣,可以看看下邊的文章:
Javascript this關鍵字使用分析
[圖解] 你不知道的JavaScript – “this”
通常在綁定事件時可以這樣使用this
document.getElementById("input1").onclick = function(e){ this.style.color="Red";};
2.2 事件對象
通過對下面的代碼調試我們可以看到,事件對象通常包含當前鍵碼等事件相關信息。值得一提的是IE的實現把事件對象放在一個全局變量window.event變量中保存,而其他遵從W3C標準瀏覽器則作為一個參數傳進處理函數。
三、 事件綁定
各瀏覽器雖然支持的方式都不太一樣,但是相比混亂的CSS,事件綁定還是有章可循的。IE有自己的實現方式,並且各版本統一,其他現代瀏覽器都按照W3C標準來實現。
3.1 傳統Dom綁定
這種方式最簡單,最有效。而且this關鍵詞指向的是當前元素。但是缺點也不少,他們是:
1.隻能綁定一次。假如我們引用的多個類庫中都對window.onload事件進行綁定,則前邊的綁定將會被後邊的覆蓋,並且常常難以察覺。
2.隻支持訂閱冒泡階段事件。
3.事件參數隻支持非IE,雖然事件對象參數僅支持非IE瀏覽器,但是我們可以使用下邊這種方式解決。
<a onclick="handle(event)" href="#">link</a>3.2 W3C標準綁定
這個是最開心的方式瞭,除IE以外的現代瀏覽器都支持,我們可以直接使用每個dom元素的addEventListener(eventName,handleFunc,trueOrFalse),第一個參數為訂閱的事件名稱,如click(沒有on),第二個參數為時間處理函數,第三個參數為是否訂閱事件捕獲階段。
下面是使用addEventListener的例子
document.getElementById("linkA").addEventListener('click', function (e) { alert('i am clicked!'); return stopDetault(e);}, false);優點:
1.支持冒泡與捕獲階段。
2.在處理函數內部,this關鍵字引用當前元素。
3.可以為同一元素的同一時間綁定多個處理函數,不會覆蓋。
缺點:
1.IE不支持
3.3 IE綁定
既然上邊提到IE不支持addEventListener,那麼肯定要找個解決方案幫幫IE小兄弟,那就是attachEvent,雖然有很多缺點,但也夠用。
下面是attachEvent的例子
document.attachEvent("onload", function () { alert("i am load");});
優點:
1.當然這個優點是和第一種綁定方式相比的:),同一元素支持多次綁定。
缺點:
1.僅支持IE事件的冒泡階段。
2.事件處理函數內部this關鍵字引用瞭window對象。要解決這個問題請繼續往下看。
3.事件名前必須加on,當然這個隻是叫法不同,也沒什麼大礙。
4.隻支持IE啊,這個很痛啊。
四、 牛人們的解決方案
Dean Edwards的方案:addEvent/removeEvent庫
這個方案比較特別。詳細請移步這裡
特點:1 it performs no object detection2 it does not use the addeventListener/attachEvent methods3 it keeps the correct scope (the this keyword)4 it passes the event object correctly5 it is entirely cross-browser (it will probably work on IE4 and NS4)6 and from what I can tell it does not leak memory
源碼:
function handleEvent(event) { var returnValue = true; // grab the event object (IE uses a global event object) event = event || fixEvent(window.event); // get a reference to the hash table of event handlers var handlers = this.events[event.type]; // execute each event handler for (var i in handlers) { this.$$handleEvent = handlers[i]; if (this.$$handleEvent(event) === false) { returnValue = false; } } return returnValue;};function fixEvent(event) { // add W3C standard event methods event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; return event;};fixEvent.preventDefault = function() { this.returnValue = false;};fixEvent.stopPropagation = function() { this.cancelBubble = true;};
【註】:這個方案有一個致命的東西,千萬不要這樣做這樣會覆蓋之前綁定的處理函數
<body onload="alert('hi');"></body>