一、事件冒泡
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">
div * {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('div')[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('div')[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>
<div>
<ul>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
</ul>
</div>
<textarea></textarea>
<p>滑鼠進入UL的任何一個子項目,如果不停止冒泡,我們從UL到SPAN都定義了滑鼠移至上方(<code>mouseover</code>)事件,這個事件會上升了UL,從而從滑鼠所進入的元素到UL元素都會有紅色的邊。</p>
<div>
<ul>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
<li><a href="#"><span>Bubbllllllllllllllle</span></a></li>
</ul>
</div>
<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>