什么是事件:
我們可以簡單的把事件理解為瀏覽器的感知系統。比如說:他可以感覺到用戶是否點擊(click)了頁面、鼠標是否進入了頁面的某個元素上面(mouseover或mouseenter)、鼠標是否離開了網頁(mouseout或mouseleave)、瀏覽器是都加載完了頁面上的資源(window.onload)、文檔樹是否生成(DOMContentLoaded)、鍵盤上的某個鍵是否按下(keydown)、鼠標的滾輪是否滾動了等等。
? 其實事件的原理并非是瀏覽器的感覺系統,它的本質是一個行為發生時,對另一個行為的回調。
事件的實現(事件綁定):
事件的綁定就是:當這個事件發生的時候,運行一個或者多個方法(function),比如說當鼠標點擊頁面的時候,就彈出一個“事件”,則寫成:
document.onclick = function(){alert("事件“);}
事件的綁定相當于做計劃,綁定在事件上的方法執行了就相當于計劃的事發生了,所以一般情況下,事件屬性的前面都有”on“,如:ele.onclick, ele.onmousedown, ele.onmouseup, 這里的on,其實就相當于:當什么時候,做計劃要早于計劃的事件發生。
當然我們也可以不給事件綁定處理方法,也就是說當此事件發生的時候,什么也不需要做,事件常有,而事件上綁定的方法不一定有,
我們給頁面中的元素的某個事件綁定處理方法的時候。經常還會有一個形式參數e,但是運行的事件,卻沒有辦法傳遞實參給這個形參e,比如:
function fn (e){
//標準瀏覽器中:定義一個形參e,但當事件觸發的時候,并沒有給e賦實際的值,則瀏覽器會把”事件“的對象賦給這個形參e,這時這個e是個系統級的對象:事件;
IE中的事件對象是個全局的屬性window.event,而標準瀏覽器的事件對象就是形參e;
所以事件對象的兼容性寫法為:e = e||window.event;
以下是常用的事件對象的屬性:
var x =e.clientX,y=e.clientY;所有瀏覽器都支持,相當于瀏覽器中鼠標的坐標;
var x=e.pageX,y = e.pageY;ie8或以下不支持,相當于文檔的中鼠標的坐標;
target事件源;事件源的概念:事件最終發生在頁面的那個元素上;
事件源和事件的傳播是息息相關的
事件的傳播包括:冒泡和捕獲;事件傳播是瀏覽器在處理事件行為的機制,冒泡階段或者捕獲階段。 注意:ie 678里,事件傳播只有冒泡階段;
var target = e.target||e.srcElement 后者是處理ie兼容‘;
div1.innerHTML="當前的事件類型:”+e.type+“鼠標的X坐標:“+x+”鼠標的Y坐標:“+Y+”事件發生在哪兒:“+e.target;
}
document.onmousemove=fn;把煩惱直接賦值給document的onmousemove這個屬性
示例2:
var ele = document.getElememtById('div1');
document.onkeydown = function(e){
e=e||wondow.event;//處理事件對象兼容性
//console。log(e.keyCode):keyCode是是當前的這個鍵值對應的ASCII碼
switch(e.keyCode){
case 37://鍵盤上的左鍵
? ? ele.style.left = ele.offserLeft-5+"px";
break;
case 38://上
ele.style.top = ele.offfsetTop-5+'px';
break;
}
}
以上這樣的事件綁定方法(就是直接把fn賦給document.onmousemove的方式)叫DOM0級事件綁定,它是相當于DOM2級事件綁定來說的
DOM元素的默認行為:
? 很多的網頁元素都會有默認的行為,比如說當你點擊一個超鏈接a0標簽的時候,他就會有一個跳轉行為;當你在網頁上點擊鼠標右鍵的時候會出現一個右鍵菜單;當你在一個form表單里點擊提交按鈕時網頁會產生一個行為病刷新網頁,當你網頁上滾動鼠標滾輪的時候,頁面的滾動條會滾動等等;這些都叫事件的默認行為,如果想把這些默認行為取消了,相應的js代碼如下:
a.onclick = function(){return false}//方法里加個 return false,就是組織超鏈接點擊時的跳轉行為了;
document.oncontextmenu = function(){
//在這里可以加一些代碼,實現自定義的右鍵菜單;
return false //系統自帶的右鍵菜單就失效了
}
Form.onsubmit = function(){return false;}//這樣表單就不會產生提交行為了;
document.onmousewheel = function(){return false;}//IE和chrome的方式,取消鼠標的滾輪的默認行為,網頁的滾動條就不會動了;
document.addEventListener('DOMMouseScoll',function(e){e.preventDefault = true;});火狐的取消滾輪的默認行為;火狐只能用Dom的二級事件綁定方式,并且用e.preventDefault = true;來取消瀏覽器滾輪的默認行為;
我們要知道常見的事件默認行為有哪些,并且要知道阻止默認行為,只要綁定到這個行為事件的方法最后加一句:return false;就可以了;
但是要強調的是:如果你的事件綁定是用addEventListener來實現的,那阻止默認行為必須用e.preventDefault = true;
事件傳播和阻止事件傳播:
? ? ? 當事件發生在子元素中的時候,往往會引起連鎖反應,就是在它的祖先元素上也會發生這個事件,比如說你點擊了一個div,也相當于點擊了一個body,同樣相當于點擊了HTML,同樣相當于點擊了document,
在理解事件傳播的時候要注意兩點:
一、是事件本身在傳播,而不是綁定在事件上的方法在傳播;
二、是并非所有的事件都會傳播,像onfocus,onblur等事件就不會傳播,onmouseenter和onmouseleave事件也不會傳播。
事件委托:
事件委托是利用事件的傳播機制,通過判斷事件源來實現的,是一種高性能的事件處理方式。對事件委托的好處和概念詳見《高程3》的第402頁;
我們通過一個簡單的示例來看看事件的好處。
需求:在如下的HTML代碼中,當你點擊這個頁面中的一個元素時,彈出這個元素對應的標簽名;
<body>
<div id='outer'>
outer
<div id = 'inner'>
inner
<p>ppppp
<span>span
<a href='###'>張松</a>
</span>
</p>
</div>
</div>
</body>
一般的思路是把所有的元素都獲取到,然后循環綁定,這樣做的缺點就是不僅性能不好,并且還要處理事件傳播的問題,不優化的代碼如下;
var eles = document.getElementsByTagNmae('*');
for(var i=0;i<eles.length;i++){
? ?eles,item(i).onclick = function(e){
alert(this.tagName);
e.stopPropagation();//加上阻止事件傳播是可以的,但是性能不是最優的;
return false;//阻止超鏈接的默認行為;
}
}
以下用事件委托實現
事件委托:事件委托就是利用事件傳播的機制,無論哪一個頁面元素,他的click事件都會最終傳播到document上;這樣,只需要在document上處理click事件即可;
document.onclick = function(e){
e = e||window.event;
var target = e.target||e.srcElement;//獲得事件源是關鍵;
alert(target.nodeName);
return false;
};
事件委托的關鍵是理解號事件的事件源的概念;
DOM二級事件
DOM 是解決文檔里元素關系的一套模式,其實那只是dom的第一個版本解決的問題。在dom的第二個版本里,解決的問題就不僅僅是文檔里元素之間的關系了,還把dom元素的事件問題也重新給了一套方案,這套方案就叫做’dom二級事件‘;
DOM二級事件解決了原來的同意事件綁定多個處理方法時,后面綁定的會覆蓋前面綁定的問題,如:
ele.onclick = fn1;
ele.onlcik=fn2;
這樣的處理的結果就是,ele的onclick事件上,fn2方法把fn1方法給覆蓋了,這樣就不容易實現同一個事件上綁定多個方法。
W3C給出的方法是:ele.addEventListener('click',fn,false);
ie6/7/8給出的方法是:ele.attachEvent('onclick',fn);
解決標準瀏覽器和低版本的IE方案如下:
事件綁定:
function bind(ele,type,handler){//標準瀏覽器
? ? ?if(ele.addEventListener){
ele.addEventListener('type',handler,false);
}else if(ele.attachEveent){//IE專用
? ? ele.attachEvent('on'+type,handler)
}
}
移除事件綁定:
function unbind(ele,type,handler){
? if(ele.removeEventListener){
? ?ele.removeEventListener('type',handler,false);
}else if(ele.detachEvent){
? ? ele.detachEvent('on'+type,handler);
}
}
注意:雖然IE、谷歌、火狐等瀏覽器都給出了DOM2級事件的處理方法,但是IE的方法卻存在著很多問題,并且非常嚴重。一、被綁定的方法在事件觸發執行時,this關鍵字竟讓是window,二、IE中被綁定到事件上的方法的執行順序是混亂的。
在W3C的標準是在同一事件上,先綁定的方法先執行,并且不能重復綁定同一個方法在同一個事件上,但是IE6、7、8中,如果綁定的方法少于9個,執行的順序是相反的,超過9個,執行順序就是混亂的,這些IE中的問題都是比較嚴重的,我們必須解決好。
事件的兼容性問題總結:(常見的)
* 事件對象本身:標準瀏覽器是事件發生時自動給方法傳遞一個實參,這個實參就是事件對象,IE是全局的window.event;
* 阻止事件傳播:標準e.stopPropagation這個方法;IE是e.cancelBubble = true這個屬性;
* 阻止默認行為:e.preventDefault()方法,IE是e.returnValue = false;
* 事件源:e.target IE是e.srcElement;
* e.pageX ,e.pageY ?IE不支持這兩個屬性
*DOM二級的事件綁定e.addEventListener ,IE ele.attachEvent;
*IE 的attachEvent 綁定的方法上:1、this不是當前的元素 ?2、執行順序是混亂的
DOM二級事件兼容性問題解決之一:解決this關鍵字
注意:以下代碼中的handler是個形參,他可以表示不同的方法,所以不要把handler認為是某一個具體的方法。如果你bind(ele,'click',fn1),則fn1就是handler,如果bind(ele,'click',fn2),那么fn2就是handler。新手一定要理解好把握好;
關鍵思路:關鍵是兩個問題,一、理解好call的用途和作用,二、理解好在一個程序里寫的代碼,是為了解決另一個程序中的問題的這種思路。
我們修改一下上面寫過的bind方法;
如果只是解決被綁定的方法的this指向,倒是好辦,只需要在IE專用的代碼里做如下修改就可以,
else if (ele.attachEvent){
? ?function fnTemp = function(){handler.call(ele)};//使用call方法,強制使handler方法在運行時this指向被綁定的ele這個DOM元素
ele.detachEvent('on'+type,fnTemp);//再綁定時,就不是直接綁定handler這個方法了,而是綁定經過’化裝‘的fnTemp這個方法
}
或者直接寫:
else if(ele.detachEvent){
? ?ele.detachEvent('on'+type,function(){handler.call(ele)};
}
這樣確實在事件觸發時,handler運行了,并且讓handler的this指向了被綁定的元素ele,但是由于我已經不是直接綁定的handler方法,而是經過call變形后的fnTemp方法,那在移除綁定的時候,我們就沒有辦法移除handler方法了。
那我們怎么能找到這個化裝之后的fntemp,并將其移除呢?這件事必須要在綁定事件的時候就要考慮好,在bind方法里,把fnTemp方法保存下來,并且還要用某種方式識別出來這個fnTemp方法是由那個handler’變形。而來的。
這是個不好解釋的難點,做這件事,我們需要兩步;
一、把fnTemp保存下來,不能使用全局變量保存,因為容易被污染,保存在bind的某個變量里,局部變量在unbind這個作用域內也訪問不到,在不同的作用域里,還能訪問到一個不是全局變量的值,那用什么呢?最好的方式就是把fnTemp保存在ele這個DOM元素的屬性上,因為這個ele是兩個函數都要操作的引用類型的變量,那么我們在ele上定義一個自定義屬性,那這個屬性在bind和unbind兩個作用域里面都能訪問到,具體實現方式如下:
else if(ele.attachEvent){
? ?if(!ele['aBind'+type){//如果不存在這個屬性則創建一個
? ? ? ?ele['aBind'+type] = [];//使用數組來保存被綁定到不同事件上的那些方法(相當的事件上,可能會被綁定很多個handler)
//這個屬性是以‘aBind"為前綴,以type為區分符的,Type是事件的類型,這是一個非常重要的技巧
}
var tempFn =function(){handler.call(ele)};讓這個方法運行是this關鍵字指向被綁定的元素;
ele['aBind'+type].push(tempFn);把變形后的tempFn保存在數組里
ele.attachEvent('on'+type,tempFn);
}
補充:理解好ele['aBind'+type]=[]這個屬性定義時,’aBind‘這個字符串是區別符的意思。
先看如果沒有這個區別符會怎么樣?那就定義了ele[type]=[];如果type是’click‘,則會出現ele.click=[];而ele本身就有click這個方法屬性,我們就沒有辦法修改這個原生的屬性,這樣定義就失效了。所以才給type前面加上'aBind’區別符作為前綴,以避免或減少和原生的屬性的沖突,這種加前綴的技巧還是很常見。
二、是給tempFn再添加一個自定義屬性,用來標示當前這個tempFn是由handler變形而來的
var tempFn= function(){handler.call(ele);}
tempFn.photo = handler ;//我們通過tempFn的photo這個屬性,就可以分辨出tempFn這個方法是由那個handler變形而來的,photo這個屬性只是在這定義,而使用它是在unbind函數里。
三、還要強調一個DOM2級事件綁定變成的原則,即:一個函數不能重復綁定在同一個事件上。比如:不能把fn1這個函數重復的綁定給ele的click事件;
ele.addEventListener('click',fn1,false);
ele.addEventListener('click',fn1,false);//綁定兩次或者多次,在事件觸發時,后面的綁定是無效,
當然,低版本的IE瀏覽器是沒有遵循這個原則的,所以這兒還要解決以下這個問題,加一個判斷即可
for(var i=0;i<ele['aBind'+type].length;i++){
if(ele['aBind'+type][i].photo==handler){//如果數組中已經存在了經過化妝的handler方法,則退出循環;
return;//保證一個方法只能被綁定到某個事件上一次;
}
}
Bind方法的完整代買如下:
function bind(ele,type,handler){
? ?if(ele.addEventListener){
? ? ? ele.addEventListener(type,handler,false);
}else if(ele.attachEvent){
? ? if(!ele['aBind'+type]){ele['aBind'+type]=[];}
var tempFn = function(){handler.call(ele)};
tempFn.photo=handler;
for(var i=0;i<ele['aBind'+type].length;i++){
? if(ele['aBind'+type][i].photo ==handler){return;}
}
ele['aBind'+type].push(tempFn);
ele.attachEvent('on'+type,tempFn);
}
}
我們再來看unbind,該做的準備工作,都已經在bind里完成了。unbind負責把綁定的在事件上的方法移除,但現在已經不是移除handler這個方法了,而是移除經過化裝后的這個方法。這個方法保存在ele的['aBind'+type這個屬性上。
我們知道ele['aBind'+type]這是個數組,先把它取到。賦值給一個短變量a:var a=ele['aBind'+type]。操作a這個短變量比操作ele['aBind'+type]這個屬性更方便。
然后遍歷這個數組,逐個比較那個是經過化裝的handler方法,當然比較的一句是photo這個屬性所以是:
for (var i=0;i<a.length;i++){
? ? ?if(a[i].photo==handler){
? ? ? ? ? ?ele.detachEvent('on'+type,a[i]);
? ? ?a.splice(i,1);//移除時間之后一定要把這個方法從數組里移除了,要不然下一次就不能再綁定了。
return;//以為每一次綁定都是唯一的(bind上講到過),所以移除后直接結束這個函數的運行就可以了;
}
}
因為用splice會造成“數組塌陷”,所以在正式的代碼里,用a[i]=null來解決的,
完整的unbind代碼如下:
function unbind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener(type,handler,false);
}else if(ele.detachEvent){
var a=ele['aBind'+type];
if(a){
for(var i=0;i<a.length;i++){
if(a[i].photo==handler){ele.detachEvent('on'+type,a[i]);a[i] =null;return;
}
}
}
}