綱要javascript
理解事件流html
使用事件處理程序java
不一樣的事件類型編程
javascript和html的交互是經過事件實現的。事件就是文檔或瀏覽器窗口發生的一些特定交互瞬間。可使用偵聽器(事件處理程序)預約事件,以便事件發生時執行相應的代碼。segmentfault
瀏覽器發展到第四代時(IE4和Netscape communicator4)都認爲,當你單擊某個按鈕時,在單擊這個按鈕的同時,你也單擊了包含這個按鈕的容器甚至整個頁面。就如同你用手指指向一同心圓的圓心,你指向的不單是一個圓,而是以這個圓心爲圓心的全部圓。
所謂事件流,指的是頁面接收事件時的順序。IE提出的事件流是事件冒泡流,Netscape communicator提出的事件流是事件捕獲流。瀏覽器
1.1 事件冒泡流編程語言
事件開始時,由最具體的元素(文檔中嵌套層次最深的哪一個節點)接收,逐級上傳到最不具體的元素(文檔)。函數
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id='myDiv'>click me</div> </body> </html>
上段代碼,按照冒泡流的說法,就是你點擊div時,沿着DOM樹,你也點擊了body,html,document。測試
1.2 事件捕獲流this
接收事件的順序,由不太具體的節點先接收,逐級向下傳到具體的節點。其用意就是在事件到達目標以前先捕獲他。
1.3 DOM事件流
'DOM2級事件',規定事件流包括三個階段。事件捕獲階段、處於目標階段、事件冒泡階段。在事件冒泡階段對事件作出響應。單擊<div>元素會按下圖所示順序觸發事件。
在DOM事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件,這意味着在捕獲階段,事件從document到<html>再到<body>就中止了。下一個階段是’處於目標‘階段,因而事件在<div>上發生,並在事件處理中被當作是冒泡階段的一部分,而後冒泡階段發生,事件又傳播迴文檔。
多數支持DOM事件流的瀏覽器都實現了一種特定的行爲:即便"DOM2級事件"規範明確要求捕獲階段不會涉及事件目標,但IE九、Safari、Chrome、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件對象上的事件。結果,就有兩個機會在目標對象上面操做事件。(注意,IE8及更早版本不支持DOM事件流)
下面這段程序,能夠看出在捕獲階段(機會1)和冒泡階段(機會2)都在目標對象上操做(觸發)了事件
運行這段程序,分別點擊紅黃綠區域,感覺下DOM事件執行的順序:(在DOM2級事件處理程序中,綁定在目標元素上的事件執行順序是按事件的註冊前後順序執行的,因此alert3.2在3.1以前。)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="box" style="background:red;width:200px;height:200px;"> <div id="outer" style="background:yellow;width:150px;height:150px;"> <div id="inner" style="background:green;width:70px;height:70px;">click me</div> </div> </div> <script type="text/javascript"> window.onload=function(){ var Box=document.getElementById('box'); var outer = document.getElementById("outer"); var inner = document.getElementById('inner'); //true爲事件捕獲,false爲事件冒泡 Box.addEventListener("click",function(){ alert('1'); },true) Box.addEventListener("click",function(){ alert('4'); },false) outer.addEventListener("click", function(){ alert("2"); }, true); inner.addEventListener("click",function(){ alert('3.2') },false); inner.addEventListener("click", function(){ alert("3.1"); }, true); } </script> </body> </html>
事件就是用戶或瀏覽器自身執行的某種動做。如click,load,mouseover等都是事件的名字。而響應某個事件的函數叫事件處理程序。事件處理程序的名字以"on"開頭,所以click事件的事件處理程序就是onclick。爲事件指定處理程序的方式有好幾種。
2.1 HTML事件處理程序
某個元素支持的每種事件,均可以使用一個與之相應 事件處理程序 同名的 HTML 特性(屬性)來指定。這個特性的值應該是能執行的Javascript代碼。例如,要在按鈕被單擊時執行一些Javascript:<input type="button" value="Click me" onclick="alert('hello world')"/>。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <script type="text/javascript"> function showMessage(){ alert('hello world'); } </script> <div id="box"> <input type="button" value="Click me" onclick="showMessage()"/> </div> </body> </html>
在HTML中指定事件處理程序有兩個缺點
缺點一 時間差,HTML加載完成,但js還未加載完成(通常狀況,HTML是按註冊順序加載的),若是在showMessage()函數解析前就單擊事件,便會引起錯誤。
解決辦法:將HTML事件處理程序封裝在一個try-catch塊中
<input type="button" value="Click me" onclick="try{showMessage();}catch(ex){}"/>
/番外篇/
try...catch 的做用是測試代碼中的錯誤。
語法:
try { //在此運行代碼 tryStatements } catch(err) { //在此處理錯誤 catchStatements }
參數:
tryStatements 必選項,可能發生錯誤的代碼
catchStatements 可選項,發生tryStatements中關聯的錯誤後執行的代碼
示例一,請點擊肯定/取消
<html> <head> <script type="text/javascript"> var txt="" function message() { try { //正確是 alert,錯誤代碼即 adddlert adddlert("Welcome guest!") } catch(err) { txt="There was an error on this page.\n\n" txt+="Click OK to continue viewing this page,\n" txt+="or Cancel to return to the home page.\n\n" //confirm(message)---要在 window 上彈出的對話框中顯示的純文本(而非 HTML 文本) if(!confirm(txt)) { document.location.href="https://segmentfault.com/u/xiakejie" } } } </script> </head> <body> <input type="button" value="View message" onclick="try{message();}catch(err){}" /> </body> </html>
語法:
try { tryStatements} catch(exception){ catchStatements} finally { finallyStatements}
參數:
finallyStatements:可選項,當tryStatements,catchStatements皆拋出錯誤執行的代碼
示例二 與稍後講的跨瀏覽器處理程序比較,與if...elaeif...else比較(對這裏的方法若不理解,後面會講到)
<html> <head> </head> <body> <input id='b1' type='button' value='按鈕'/> <script> (function(){ var oBtn=document.getElementById("b1"); function mto(){ alert("123"); }; try { oBtn.attachEvent("onclick",mto); } catch(e) { oBtn.addEventListener("click",mto,false); } finally //DOM0級事件處理程序 { oBtn.onclick=mto; } })(); </script> </body> </html>
缺點二 HTML與javascript代碼緊密耦合,若是要更換事件處理程序,HTML部分和javascript部分都要修改。(另一個缺點會在後續另篇文章解析)
2.2 DOM0級事件處理程序
經過Javascript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這種爲事件處理程序賦值的方法,在第四代WEB瀏覽器出現,至今全部的瀏覽器都支持。要使用javascript指定事件處理程序,首先必須取得一個要操做的對象的引用。
每一個元素(包括window document)都有本身的事件處理程序屬性,這些屬性一般所有小寫,例如onclick。以下
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <div id="box"> <input type="button" value="Click me" id="btn" /> </div> <script type="text/javascript"> function showMessage(){ alert('DOM0級事件處理程序'); } var oBtn=document.getElementById('btn'); oBtn.onclick=showMessage; </script> </body> </html>
在此咱們經過一個文檔對象取得一個按鈕的引用,而後爲他指定了onclick事件處理程序,但要注意在這些代碼(好比這裏的showmessage函數代碼)運行之前不會指定事件處理程序(指的是當你點擊按鈕時,不會執行showmessage函數代碼,由於這些代碼有可能尚未運行),所以若是這些代碼在頁面中位於按鈕後面,有可能在一段時間內怎麼點擊都沒有反應。
2.2.1 番外篇
不少同窗或許納悶 oBtn.onclick=showMessage;
與 oBtn.onclick=showMessage(); 區別
showMessage是一個函數名,ECMAscript中函數是一個對象,函數名則是一個指向函數對象的指針,使用不帶括號的函數名是訪問函數指針,而不是調用函數。若要調用則帶括號。
我是這樣理解的 var funName=function(){...};
是一個函數表達式,這個函數表達式是將一個匿名函數賦值給一個變量。在JS是面向對象編程語言,變量也是對象。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <script type="text/javascript"> var fnName=function(){ alert('Hello World'); }(); </script> </body> </html>
上例咱們看出函數表達式後加()直接調用。這樣理解 var funName=function(){...};
即是一個函數表達式,oBtn.onclick=showMessage();
這樣即是把他當作一個函數表達式,而且直接執行這個函數,而不是DOM0級事件處理程序。這塊知識我在另外一篇文章中有詳細講解 https://segmentfault.com/a/11...
2.2.2 DOM0級事件處理程序的做用域
使用DOM0級方法指定事件處理程序被認爲是元素的方法(之因此稱元素的方法,是由於使用DOM0級事件處理程序必須首先得到一個操做對象,這個操做對象也就是DOM元素)。所以這時事件處理程序是在元素的做用域中進行。那麼,程序中的this引用當前元素。在事件處理程序中能夠經過this訪問元素的任何屬性和方法。DOM0級事件處理程序會在事件流的冒泡階段被處理。
var oBtn=document.getElementById('btn'); oBtn.onclick=function(){ alert(this.id) };
2.2.3 刪除DOM0級事件處理程序
oBtn.onclick=null;
2.3 DOM2級事件處理程序
DOM2級事件處理程序定義了兩個方法:用於處理指定和刪除事件處理程序的操做,addEventListener()和removeEventListener()。全部的DOM節點都包含這兩個方法。都接收三個參數:要處理的事件名,做爲事件處理程序的函數和布爾值。true表示在捕獲階段調用事件處理程序,false表示在冒泡階段調用事件處理程序。使用DOM2級方法指定事件處理程序也被認爲是元素的方法,事件處理程序也是在元素的做用域中運行的,也能夠經過this訪問元素的任何屬性和方法。
2.3.1 DOM2級事件處理程序優勢
能夠添加多個事件處理程序。而且按添加的順序觸發。
var oBtn=docunment.getElementById('myBtn'); oBtn.addEventListener('click',function(){alert(this.id)},false); oBtn.addEventListener('click',function(){alert('hello')},false);
2.3.1 DOM2級事件處理程序的移除
經過addEventListener()添加的事件處理程序只能用removeEventListener()移除,而且傳入參數要與添加處理程序參數徹底相同。意味着經過abbEventListener()添加的匿名函數沒法被移除。
爲了兼容各類瀏覽器咱們大多數將事件處理程序添加到事件流的冒泡階段。除非爲了在事件到達目標前捕獲他,纔將事件處理程序添加到捕獲階段。
2.3.2 支持的瀏覽器
IE9,Firefox,Safari,Chrome和Opers支持DOM2級事件處理程序。
2.4 IE事件處理程序
IE事件處理程序,指定和刪除事件處理程序方法:attachEvent()和detachEvent().接受相同的兩個參數,事件處理程序名稱與事件處理程序函數,(注意DOM2級第一個參數是事件名)。經過attachEvent()添加的事件處理程序都會被加到冒泡階段,是由於IE8及更早版本只支持冒泡事件流
使用attachEvent()爲按鈕添加一個事件處理程序.
var bnt = document.getElementById('bnt'); btn.attachEvent('onclick',function(){alert("hello");});
IE事件處理程序與DOM0級事件處理程序主要區別:事件處理程序的做用域。使用DOM0級方法,事件處理程序是在其所屬元素內運行;attachElent()方法時,事件處理程序是在全局做用域中運行,this值所以等於Window. 在編寫跨瀏覽器代碼時牢記這一區別(跨瀏覽器稍後會說到)
同DOM2級事件處理程序同樣,移除事件只能經過detachEvent()移除,而且傳入的參數要同樣。
2.4.1支持的瀏覽器
IE和Opera
2.5跨瀏覽器事件處理程序
回顧一下前面的幾種事件處理程序。HTML事件處理程序,DOM0級事件處理程序,DOM2級事件處理程序,IE事件處理程序。
HTML事件處理程序,適合全部瀏覽器;DOM0級,適合全部瀏覽器;DOM2級,適合IE9,Firefox,Safari,Chrome和Opers;IE事件處理程序,適合IE和Opera。
2.5.1 跨瀏覽器事件處理程序
爲了以跨瀏覽器方式處理事件,方法一,使用可以隔離瀏覽器差別的javascript庫。方法二,本身開發最適合的處理方法。咱們這裏使用的即是方法二。運用能力檢測思想。咱們要保證處理事件的代碼在大多數瀏覽器下一致的運行,只需關注冒泡階段(由於全部現代瀏覽器都支持事件冒泡)。
在講跨瀏覽器事件處理程序以前,咱們先溫習一下Object。建立Object實例的方式有兩種
使用NEW操做符後跟Object實例,var person = new Object(); person.name="liMing"; person.age = 29;
第二種是使用對象字面量表示法,var person = {name:"liMing",age:29};
訪問對象屬性方法也有兩種
點表示法。alert(person.name);
第二種是是方括號表示法,alert(person['name']);//優勢,能夠經過變量來訪問屬性
建立一個對象eventUtil,對象包含兩個方法,一個方法給元素添加事件addHeadler(),另外一個方法給元素去除事件removeHeader()。方法接受三個參數,要操做的元素,事件名稱,和事件處理程序函數。
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.addEventListener(type,handler,false); }else if (element.attachEvent) { element.attachEvent('on'+type,handler); }else { element['on'+type]=null; } } };
引用:EventUnit.addHandler(element,'click',handler);
簡析:能夠將EventUnil當作使用對象字面量法建立的Object實例。addHandler,removeHandler當作對象的屬性。在ECMAScript中函數名自己就是變量,因此函數也能夠做爲值來使用,因此能夠講function(evement,type,handler){}看做是屬性的值。由於ECMAScript中函數能夠傳進來多個參數,參數的數據類型不限制,因此這裏能夠傳入對象eventment,字符串type,函數/對象handler.
引用形式的簡析:
建立一個對象:var person = {name:'liMing'}; 也能夠var person=new Object(); person.name='liMing';
那麼EventUnil.addHandler(element,'click',handler)能夠理解爲var EventUnil.addHandler=function(){};這一函數表達式的調用。注,以上簡析均是我的理解,若不正確,但願指正。
在觸發DOM上的某個事件時,會產生一個事件對象Event,這個對象包含着全部與事件有關的信息。全部的瀏覽器都支持Event對象,但支持的方式不一樣。
3.1 DOM中的事件對象
不管指定事件處理程序時使用什麼方法(DOM0,DOM2),都會傳入Event對象。event對象包含與建立它的特定事件有關的屬性和方法,觸發的事件類型不同,可用的屬性和方法也不同,不過,全部事件都會有下表列出的成員
3.1.1 currentTarget 與 target
在事件處理程序內部,對象this始終等於currentTarget的值,而target則只包含事件的實際目標。事件處理程序存在於按鈕的父節點中(例如document.body)
document.body.onclick=function(event){ alert(event.currentTarget===document.body);//true alert(this===document.body);//true alert(event.target===document.getElementById('myBtn')); //true }
該結果是因爲按鈕上沒有註冊事件處理程序,點擊按鈕時,click事件冒泡到了document.body,在那裏事件才獲得處理。
3.1.2 type
在須要一個函數處理多個事件時,可以使用type屬性
var btn = document.getElementById('myBtn'); var handler = function(event){ switch (event.type){ case 'click'; alert('clicked'); break; case 'mouseover'; event.target.style.backgroundColor = 'red'; break; case 'mouseout'; event.target.style.backgroundColor = ''; break; } }; btn.click = handler; btn.mouseover = handler; btn.mouseout = handler;
3.1.3 阻止事件的默認行爲 preventDefault()
取消特定事件的默認行爲。例如,連接的默認行爲就是在被單擊時會導航到href特性指定的URL,若是你想阻止連接導航這一默認行爲,經過連接的onclick事件處理程序取消它。
var link = document.getElementById('myLink'); link.onclick = function(event){ event.preventDefault(); };
3.1.4 取消進一步事件的捕獲或冒泡 stopPropagation()
在講currentTarget與target時,咱們知道按鈕沒有註冊事件處理程序,click事件冒泡到了document,body上。如今咱們能夠阻止冒泡行爲了,結果避免觸發註冊在document.body上的事件處理程序
var btn = document.getElementById('myBtn'); btn.onclick = function(event){ event.stopPropagation(); }; document.body.onclick = function(event){ alert(event.currentTarget===document.body); alert(this===document.body); alert(event.target===btn); };
3.1.5 肯定事件位於當前事件流那個階段eventPhase
注意,只有在事件處理程序執行期間,event對象纔會存在,一旦事件處理程序執行完畢,event對象便會被銷燬。
3.2 IE中的事件對象
要訪問IE中的event對象有幾種不一樣的方式,訪問的方式取決於指定事件處理程序的方法。
使用DOM0級方法添加事件處理程序。event對象做爲window對象的一個屬性存在。
var btn = document,getElementById('myBtn'); btn.onclick = function(){ var event = window.event; alert(event.type); //"click" };
使用IE方法添加事件處理程序,event對象做爲參數傳入事件處理程序中。
var btn = document,getElementById('myBtn'); btn.attachEvent ("onclick",function(event){ alert(event.type);//"click" });
經過HTML特性指定事件處理程序,經過一個名爲event變量訪問event對象(與DOM中的事件模型相同)
<input type="button" value="click me" onclick="alert(event.type)"/>
3.2.1 IE中event對象的屬性和方法
其中不少屬性和方法都有對應的或相關的DOM屬性和方法,與DOM的event對象同樣,這些屬性和方法也會由於事件類型的不一樣而不一樣,但全部事件對象都會包含下表所列的屬性和方法
3.2.2 事件目標
由於事件處理程序做用域是根據指定他的方式肯定的,因此不能認爲this始終等於事件目標。因爲個人瀏覽器版本問題,書中的現象復現不了,因此用下程序代替。
var btn = document.getElementById('myBtn'); function showMes(event){ var event = window.event||event; var ele = event.target||event.srcElement; alert(ele === this); }; var eventUnit = { addHandler: function(element,type,handler){ if (element.attachEvent) { element.attachEvent(type,handler) }else{ element[type]=handler; } } }; eventUnit.addHandler(btn,'onclick',showMes);
3.2.3 取消事件的默認行爲,returnValue
returnValue屬性至關與DOM中的preventDefault()方法,只要將returnValue設置爲falsae就能夠阻止事件的默認行爲。一樣由於瀏覽器版本問題,書中代碼修改以下:
var link = document.getElementById('myLink'); function stopEvent(event){ var event = event||window.event; event.returnValue = false; event.preventDefault(); }; link.onclick = stopEvent;
3.2.4 阻止事件冒泡 cancelBubble
cancelBubble屬性與DOM中的stopPropagatioin()方法做用相同,IE不支持事件捕獲,因此只用取消事件冒泡。事件處理程序中將cancelBubble設置爲true即可以阻止事件冒泡行爲。
3.3 跨瀏覽器的事件對象
<body> <input type="button" name="button" id="myBtn" value="button"> <a href="http://www.w3school.com.cn" id="myLink">W3School</a> <script type="text/javascript"> var btn = document.getElementById('myBtn'); var link = document.getElementById('myLink'); var EventUtil = { getEvent : function(event){ return event?event:window.event; }, getTarget : function(event){ return event.target||event.srcElement; //return event.target?event.target:event.srcElement; }, preventDefault : function(event){ if (event.preventDefault) { event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation : function(event){ if (event.stopPropagation) { event.stopPropagation(); }else{ event.cancelBubble = true; } }, }; btn.onclick = function(event){ event = EventUtil.getEvent(event); }; link.onclick = function(event){ event = EventUtil.getEvent(event); EventUtil.preventDefault(event); EventUtil.stopPropagation(event); }; btn.onclick = function (event){ event=EventUtil.getEvent(event); target = EventUtil.getTarget(event); alert('stopPropagation'); alert(target); EventUtil.stopPropagation(event); }; document.body.onclick = function(event){ alert('body clicked'); }; </script> </body>
本文參考javascript高級程序設計第三版