1. 使用簡單事件處理器 可以用幾種不同的方式處理事件。最直接的方式是用事件屬性創建一個簡單事件處理器(simple event handler)。元素為它們支持的每一種事件都定義了一個事件屬性。舉個例子,onmouseover事件屬性對應全局事件mouseover,後者會在用戶把游標移動到元素占據 ...
1. 使用簡單事件處理器
可以用幾種不同的方式處理事件。最直接的方式是用事件屬性創建一個簡單事件處理器(simple event handler)。元素為它們支持的每一種事件都定義了一個事件屬性。舉個例子,onmouseover事件屬性對應全局事件mouseover,後者會在用戶把游標移動到元素占據的瀏覽器屏幕的上方時觸發。(這是一種通用的模式:大多數事件都有一個對應的事件屬性,其名稱定義為 on<eventname>)
1.1 實現簡單的內聯事件處理器
使用某個屬性最直接的方式是給它指派一組JavaScript語句。當該事件被觸發後,瀏覽器就會執行提供的語句。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用內聯JavaScript處理事件</title> <style> p {background: gray;color:white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p onmouseover="this.style.background='white';this.style.color = 'black'"> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p> </body> </html>
此例中,指定了兩條JavaScript語句用來想要mouseover事件。具體方式是設置它們作為文檔里p元素onmouseover事件屬性的值。這些語句如下:
this.style.background='white';
this.style.color = 'black';
這些是直接應用到元素style屬性上的CSS屬性。瀏覽器會把特殊變數 this 的值設置為代表觸發事件元素的HTMLElement 對象,而style 屬性會返回該元素的CSSStyleDeclaration 對象。
如果在瀏覽器中載入這個文檔,style元素定義的初始樣式就會被應用到p元素上。當把滑鼠移至元素上方時,JavaScript語句就會被執行。
這種轉變是單向的:當滑鼠離開元素的屏幕區域時樣式不會重置。許多事件是成雙成對的。與mouseover相對的事件被稱為 mouseout,可以通過onmouseout事件屬性來處理這一事件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用內聯JavaScript處理事件</title> <style> p {background: gray;color:white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p onmouseover="this.style.background='white';this.style.color = 'black';" onmouseout="this.style.removeProperty('color');this.style.removeProperty('background');"> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p> </body> </html>
添加這些代碼後,此元素就能響應滑鼠進入和離開它所占據的屏幕區域了。
1.2 實現一個簡單的事件處理函數
為了在一定程度解決繁瑣和重覆添加問題,可以定義一個函數,並將函數名指定為元素事件屬性的值。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用函數來處理事件</title> <style> p {background: gray;color:white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p onmouseover="handleMouseOver(this)" onmouseout="handleMouseOut(this)"> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p > <p onmouseover="handleMouseOver(this)" onmouseout="handleMouseOut(this)"> 一些年輕人,通過高端消費來營造自己高端收入的形象 </p> <script> function handleMouseOver(elem){ elem.style.background='white'; elem.style.color = 'black'; } function handleMouseOut(elem){ elem.style.removeProperty('color'); elem.style.removeProperty('background'); } </script> </body> </html>
此例中,定義了一些JavaScript函數,它們所包含的語句是用來想要滑鼠事件的。並且還在onmouseover和onmouseout屬性里指定了這些函數。特殊值this 指的是觸發事件的元素。
這種方式相對於之前的技巧有了進步。重覆添加工作變得了更少了,代碼也更易閱讀了。但是,希望能讓事件與HTML元素互相分離,要做到這一點,需要在訪問DOM。
2. 使用DOM和事件對象
前面例子演示的簡單事件處理器用於基本任務是可行的,但如果想要進行更為複雜的處理(以及更優雅地定義事件處理器),就需要使用DOM和JavaScript的Event對象了。下麵例子展示瞭如何使用Event對象,以及如何用DOM來將某個函數與事件關聯起來。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用DOM構建事件處理</title> <style> p {background: gray;color:white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p > <p> 一些年輕人,通過高端消費來營造自己高端收入的形象 </p> <script type="application/javascript"> var pElems = document.getElementsByTagName("p"); for(var i=0;i<pElems.length;i++){ pElems[i].onmousemove = handleMouseOver; pElems[i].onmouseout = handleMouseOut; } function handleMouseOver(e){ e.target.style.background='white'; e.target.style.color = 'black'; } function handleMouseOut(e){ e.target.style.removeProperty('color'); e.target.style.removeProperty('background'); } </script> </body> </html>
這段腳本(必須把他移到頁尾,因為操作的是DOM)找到想要處理事件的所有元素,然後給事件處理器屬性設置一個函數名。所有事件都擁有像這樣的屬性。它們的命名方式是一致的:以 on 開頭,後接事件的名稱。
PS:請註意這裡使用函數的名稱來將它註冊成一個事件監聽器。一個常見的錯誤是把括弧加在函數名的後面,使 handleMouse變成handerMouse()。這樣做的後果是函數會在腳本執行時(而不是事件觸發時)被調用。
此例中那些處理事件的函數定義了一個名為e的參數。它會被設成瀏覽器所創建的一個Event對象,用於在事件觸發時代表該事件。這個Event對象提供了所發生的事件信息,能夠更加靈活地(相對於把代碼放在元素屬性中而言)對用戶交互行為作出反應。在此例中,用target屬性來獲取觸發事件的HTMLElement,這樣就能使用樣式屬性來改變它的外觀。
在展示事件對象之前,想演示另一種指定事件處理函數的方式。事件屬性(名稱以on開頭的那些)一般來說是最容易的方式,但也可以使用addEventListener 方法,它由HTMLElement對象實現。還可以使用removeEventListener 方法來取消函數與事件之間的關聯。可以在這兩個方法中都能把事件類型和處理它們的函數表達為參數,如下麵的例子所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用addEventListener和removeEventListener方法</title> <style> p {background: gray;color:white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p > <p id="block2"> 一些年輕人,通過高端消費來營造自己高端收入的形象 </p> <button id="btn">Press Me</button> <script type="application/javascript"> var pElem = document.getElementsByTagName("p"); for(var i=0;i<pElem.length;i++){ pElem[i].addEventListener("mouseover",handleMouseOver); pElem[i].addEventListener("mouseout",handleMouseOut); } document.getElementById("btn").onclick = function(){ document.getElementById("block2").removeEventListener("mouseout",handleMouseOut); } function handleMouseOver(e){ e.target.style.background='white'; e.target.style.color = 'black'; } function handleMouseOut(e){ e.target.style.removeProperty('color'); e.target.style.removeProperty('background'); } </script> </body> </html>
此例中的腳本使用addEventListener方法把handleMouseOver和handleMouseOut函數註冊成p元素的事件處理器。當button被點擊後,腳本用removeEventListener方法取消了id值為block2 的p元素與handleMouseOut函數之間的關聯。
addEventListener 方法的優點在於它讓你能夠訪問某些高級事件特性。下表介紹了Event對象的成員。
2.1 按類型區分事件
type 屬性會告訴你正在處理的是哪種類型的事件。這個值以字元串的形式提供,比如 mouseover。有了探測事件類型的能力,就可以用一個函數來處理多個類型了。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用type屬性</title> <style type="text/css"> p {background: gray;color: white;padding: 10px;margin: 5px;border: thin solid orange;} </style> </head> <body> <p> 你承受的苦難並不比他人多太多,痛苦主要來自敏感和脆弱。 </p > <p id="block2"> 一些年輕人,通過高端消費來營造自己高端收入的形象 </p> <script type="application/javascript"> var pElems = document.getElementsByTagName("p"); for(var i=0;i<pElems.length;i++){ pElems[i].onmouseover = handleMouseEvent; pElems[i].onmouseout = handleMouseEvent; } function handleMouseEvent(e){ if(e.type == "mouseover"){ e.target.style.background='white'; e.target.style.color = 'black'; }else if(e.type == "mouseout"){ e.target.style.removeProperty('color'); e.target.style.removeProperty('background'); } } </script> </body> </html>
此例中,只用了一個handleMouseEvent 這一個事件處理器,通過使用type屬性來判斷正在處理的是哪一種事件。
2.2 理解事件流
一個事件的生命周期包括三個階段:捕捉(capture)、目標(target)和冒泡(bubbing)。
(1) 理解捕捉階段
當某個事件被觸發時,瀏覽器會找出事件涉及的元素,它被稱為該事件的目標。瀏覽器會找出body元素和目標之間的所有元素並分別檢查它們,看看它們是否帶有事件處理器且要求獲得其後代元素觸發事件的通知。瀏覽器會先觸發這些事件處理器,然後才會輪到目標自身的處理器。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>捕捉事件</title> <style type="text/css"> p {background: gray;color: white;padding: 10px;margin: 5px;border: thin solid orange;} span {background: white;color: black;padding: 2px;cursor: default;} </style> </head> <body> <p id="block1"> 你承受的苦難並不比他人多太多,痛苦主要來自<span id="point">敏感和脆弱</span>。 </p > <script type="application/javascript"> var point = document.getElementById("point"); var textBlock = document.getElementById("block1"); point.addEventListener("mouseover",handleMouseEvent); point.addEventListener("mouseout",handleMouseEvent); textBlock.addEventListener("mouseover",handleDescendantEvent,true); textBlock.addEventListener("mouseout",handleDescendantEvent,true); function handleDescendantEvent(e){ if(e.type == "mouseover" && e.eventPhase == Event.CAPTURING_PHASE){ e.target.style.border = "thick solid red"; e.currentTarget.style.border = "thick double blue"; }else if(e.type == "mouseout" && e.eventPhase == Event.CAPTURING_PHASE){ e.target.style.removeProperty("border"); e.currentTarget.style.removeProperty("border"); } } function handleMouseEvent(e){ if(e.type == "mouseover"){ e.target.style.background='black'; e.target.style.color = 'white'; }else if(e.type == "mouseout"){ e.target.style.removeProperty('background'); e.target.style.removeProperty('color'); } } </script> </body> </html>
此例中,定義了一個span作為p元素的子元素,然後註冊了mouseover和mouseout事件的處理器。當註冊父元素(即p元素)時,給addEventListener方法添加了第三個參數,就像這樣:
textBlock.addEventListener("mouseover",handleDescendantEvent,true);
這個額外的參數告訴瀏覽器讓p元素在捕捉階段接收後代元素的事件。當mouseover事件被觸發時,瀏覽器會從HTML文檔的跟節點起步,一路沿著DOM向目標(也就是觸發事件的元素)前進。對層級里的每一個元素,瀏覽器都會檢查它是否對捕捉到的事件感興趣。可以從下圖看到示例文檔的順序。
對每一個元素,瀏覽器都會調用它所有啟用捕捉的監聽器。此例中,瀏覽器會找到並調用註冊在p元素上的handleDescendantEvent函數。當handleDescendantEvent函數被調用時,Event對象包含了目標元素的信息(通過target),還有導致函數被調用的元素(通過currentTarget屬性)。這裡同時使用了這兩個屬性,這樣就能修改p元素和其子元素span的樣式了。
事件捕捉讓目標元素的各個上級元素都有機會在事件傳遞到目標元素之前對其做出對應。上級元素的事件處理器可以阻止事件流向目標,方法是對Event對象調用stopPropagation或stopImmediatePropagation函數。這兩個函數的區別在於,stopPropagation會確保調用當前元素上註冊的所有事件監聽器,而stopImmediatePropagation會忽略任何未觸發的監聽器。為了展示瞭如何給hanldDescendantEvent事件處理器添加stopPropagation函數,修改上面例子的JavaScript代碼中的hanldDescendantEvent函數如下:
function handleDescendantEvent(e){ if(e.type == "mouseover" && e.eventPhase == Event.CAPTURING_PHASE){ e.target.style.border = "thick solid red"; e.currentTarget.style.border = "thick double blue"; }else if(e.type == "mouseout" && e.eventPhase == Event.CAPTURING_PHASE){ e.target.style.removeProperty("border"); e.currentTarget.style.removeProperty("border"); } e.stopPropagation(); }
做了這個改動後,瀏覽器的捕捉階段就會在p元素上的處理器被調用時結束。瀏覽器不會檢查其他任何元素,並且會跳過目標和冒泡階段。對此例來說,這就意味著handleMouseEvent 函數里的樣式變化不會被應用(以響應mouseover事件),如此例修改後的顯示效果如下:
請註意此例在處理器里檢查了事件類型,並用 eventPhase屬性來確定事件所處的階段,就像這樣:
... if(e.type == "mouseover" && e.eventPhase == Event.CAPTURING_PHASE){ ...
在註冊事件監聽器時啟動捕捉事件並不能停止針對元素自身的事件。此例中,p元素占據了瀏覽器屏幕空間,它同樣會響應mouseover事件。為了避免這一點,進行了檢查,確保只有在處理捕捉階段的事件(只針對後代元素的事件,處理此事件完全是因為註冊了啟用捕捉的監聽器)時才會應用樣式改動。eventPhase屬性會返回下表裡展示的三個值之一。它們代表了事件生命周期的三個階段。
(2) 理解目標階段
目標階段是三個階段中最簡單的。當捕捉階段完成後,瀏覽器會觸發目標元素上任何已添加的事件類型監聽器,如下圖所示:
這裡要註意的唯一一點是可以多次調用addEventListener函數,因此某個給定事件類型可以有不止一個監聽器。
PS:如果在目標階段調用stopPropagation或stopImmediatePropagation函數,相當於終止了事件流,不再進入冒泡階段。
(3) 理解冒泡階段
完成目標階段之後,瀏覽器開始轉而沿著上級元素鏈朝 body元素前進。在沿途的每個元素上,瀏覽器都會檢查是否存在針對該事件類型但沒有啟用捕捉的監聽器(也就是說,addEventListener函數的第三個參數是 false)。這就是所謂的事件冒泡。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件冒泡</title> <style type="text/css"> p {background: gray;color: white;padding: 10px;margin: 5px;border: th