javascript事件綁定學(xué)習(xí)要點(diǎn)
事件綁定分為兩種:一種是傳統(tǒng)事件綁定(內(nèi)聯(lián)模型,腳本模型),一種是現(xiàn)代事件綁定(DOM2級(jí)模型)?,F(xiàn)代事件綁定在傳統(tǒng)綁定上提供了更強(qiáng)大更方便的功能。
一 傳統(tǒng)事件綁定的問(wèn)題
傳統(tǒng)事件綁定中的內(nèi)聯(lián)模型不做討論,基本很少去用。先來(lái)看一下腳本模型,腳本模型將一個(gè)函數(shù)賦值給一個(gè)事件處理函數(shù)。傳統(tǒng)綁定如:
window.onload=function(){ var box=document.getElementById('box'); box.onclick = function(){ alert('Lee'); }; };
問(wèn)題一:一個(gè)事件處理函數(shù)觸發(fā)兩次事件
如果一個(gè)頁(yè)面有兩個(gè)或者多個(gè)js,并且第一個(gè)js是第一個(gè)程序開(kāi)發(fā)的,第二個(gè)js是第二個(gè)程序員開(kāi)發(fā)的。第一個(gè)window.onload被覆蓋了,如
window.onload=function(){ alert('Lee'); }; window.onload=function(){ alert('Mr.lee'); }
結(jié)果只是打印了 Mr.lee
其實(shí)是有辦法解決這個(gè)問(wèn)題的,看下面這兩種形式。
a:
alert(window.onload);//一開(kāi)始沒(méi)有注冊(cè)window.onload,那么就是null window.onload=function(){ alert('Lee'); }; alert(window.onload);//如果已經(jīng)有window.onload,打印的是函數(shù)function window.onload=function(){ alert('Mr.lee'); }
b:
alert(typeof window.onload);//一開(kāi)始沒(méi)有window.onolad,舊版火狐顯示undefined,新版顯示object, window.onload=function(){ alert('Lee'); }; alert(typeof window.onload);//如果已經(jīng)有window.onload,所有瀏覽器都會(huì)顯示function window.onload=function(){ alert('Mr.lee'); }
所以解決辦法有了。
window.onload=function(){ alert('Lee'); }; if(typeof window.onload=='function'){ var saved=null;//保存上一個(gè)事件對(duì)象 saved=window.onload; } //saved 就是window.onload,saved()相當(dāng)于window.onload(),但是window.onload()不能執(zhí)行的 //所以saved()相當(dāng)于window.onload=function(){} window.onload=function(){ if(saved){ saved();//執(zhí)行上一個(gè)事件 window.onload=function(){} } alert('Mr.lee'); //執(zhí)行本事件 }
問(wèn)題二:事件切換器
切換一個(gè)id為box的div,讓里面的背景red與blue直接切換,并且切換之前彈框一次,如:
window.onload=function(){ var box=document.getElementById('box'); box.className="red"; box.onclick=function(){ alert('Lee'); //只執(zhí)行了一次 blue.call(this);//通過(guò)匿名函數(shù)執(zhí)行某一函數(shù),那么里面的this就是代表的window,所以可以通過(guò)call傳遞 }; } function blue(){ this.className="blue"; this.onclick=red; } function red(){ this.className="red"; this.onclick=blue; }
上面的代碼雖然實(shí)現(xiàn)了切換功能,但是彈框只執(zhí)行了一次。
//添加事件函數(shù) //obj相當(dāng)于window //type相當(dāng)于onload //fn相當(dāng)于function(){} function addEvent(obj,type,fn){ //用于保存上一個(gè)事件 var saved=null; if(typeof obj['on'+type]=='function'){ saved=obj['on'+type];//保存上一個(gè)事件 } obj['on'+type]=function(){ if(saved){ saved(); } fn.call(this); } } addEvent(window,'load',function(){ var box=document.getElementById("box"); //addEvent(box,'click',function(){ //目的達(dá)到,每次都執(zhí)行了,沒(méi)有被覆蓋 // alert('ss'); //}); addEvent(box,'click',blue); }); function red(){ this.className="red"; addEvent(box,'click',blue); } function blue(){ this.className="blue"; addEvent(box,'click',red); } //當(dāng)不停的切換的時(shí)候,瀏覽器突然卡死,并且報(bào)錯(cuò):too much recursion,太多的遞歸 //因?yàn)榉e累了太多的保存的事件 //解決方案,就是用完的事件,就立刻移除掉
按照上面的代碼出現(xiàn)了注釋中的錯(cuò)誤,解決的辦法如下:
//添加事件函數(shù) //obj相當(dāng)于window //type相當(dāng)于onload //fn相當(dāng)于function(){} function addEvent(obj,type,fn){ //用于保存上一個(gè)事件 var saved=null; if(typeof obj['on'+type]=='function'){ saved=obj['on'+type];//保存上一個(gè)事件 } obj['on'+type]=function(){ if(saved){ saved(); } fn.call(this); } } //當(dāng)不停的切換的時(shí)候,瀏覽器突然卡死,并且報(bào)錯(cuò):too much recursion,太多的遞歸 //因?yàn)榉e累了太多的保存的事件 //解決方案,就是用完的事件,就立刻移除掉 //移除事件函數(shù) function removeEvent(obj,type){ if(obj['on'+type]){ obj['on'+type]=null; } } addEvent(window,'load',function(){ var box=document.getElementById("box"); //addEvent(box,'click',function(){ //目的達(dá)到,每次都執(zhí)行了,沒(méi)有被覆蓋 // alert('ss'); //}); addEvent(box,'click',blue); }); function red(){ this.className="red"; removeEvent(this,'click'); addEvent(box,'click',blue); } function blue(){ this.className="blue"; removeEvent(this,'click'); addEvent(box,'click',red); }
二 W3C事件處理函數(shù)
addEventListener()與removeEventListener()
W3C事件處理函數(shù)兩個(gè),addEventListener()與removeEventListener()。
//W3C自帶的兩個(gè)添加事件和刪除事件
1.覆蓋問(wèn)題,解決
window.addEventListener('load',function(){ alert('Lee'); },false); window.addEventListener('load',function(){ alert('Mr.Lee'); },false); window.addEventListener('load',function(){ alert('Mrs.Lee'); },false);
2.相同函數(shù)屏蔽的問(wèn)題,解決
window.addEventListener('load',init,false); window.addEventListener('load',init,false); window.addEventListener('load',init,false); function init(){ alert('Lee'); }
3.是否可以傳遞this,解決
例子1:
window.addEventListener('load',function(){ var box=document.getElementById('box'); box.addEventListener('click',function(){ alert(this); },false); },false);
例子2:
window.addEventListener('load',function(){ var box=document.getElementById('box'); box.addEventListener('click',blue,false); },false); function red(){ this.className="red"; this.removeEventListener('click',red,false); this.addEventListener('click',blue,false); } function blue(){ this.className="blue"; this.removeEventListener('click',blue,false); this.addEventListener('click',red,false); }
4.添加一個(gè)額外的方法,會(huì)不會(huì)被覆蓋,或者只能執(zhí)行一次,解決
window.addEventListener('load',function(){ var box=document.getElementById('box'); box.addEventListener('click',function(){ alert('Lee'); },false); box.addEventListener('click',blue,false); },false);
綜上所述:W3C是比較完美的解決了這些問(wèn)題,非常好用,但是IE8和之前的瀏覽器并不支持,而是采用了自己的事件,當(dāng)然IE9已經(jīng)完全支持了W3C的這兩個(gè)事件處理函數(shù)。
W3C可以設(shè)置冒泡和捕獲方式。
支持W3C標(biāo)準(zhǔn)的瀏覽器在添加事件時(shí)用addEventListener(event,fn,useCapture)方法,基中第3個(gè)參數(shù)useCapture是一個(gè)Boolean值,用來(lái)設(shè)置事件是在事件捕獲時(shí)執(zhí)行,還是事件冒泡時(shí)執(zhí)行。而不兼容W3C的瀏覽器(IE)用attachEvent()方法,此方法沒(méi)有相關(guān)設(shè)置,不過(guò)IE的事件模型默認(rèn)是在事件冒泡時(shí)執(zhí)行的,也就是在useCapture等于false的時(shí)候執(zhí)行,所以把在處理事件時(shí)把useCapture設(shè)置為false是比較安全,也實(shí)現(xiàn)兼容瀏覽器的效果。
事件捕獲階段:事件從最上一級(jí)標(biāo)簽開(kāi)始往下查找,直到捕獲到事件目標(biāo)(target)。
事件冒泡階段:事件從事件目標(biāo)(target)開(kāi)始,往上冒泡直到頁(yè)面的最上一級(jí)標(biāo)簽。
事件的傳播是可以阻止的:
在W3c中,使用stopPropagation()方法
在IE下設(shè)置cancelBubble = true;
三.IE事件處理函數(shù)
attachEvent()和detachEvent()
IE實(shí)現(xiàn)了與DOM中類似的兩個(gè)方法:attachEvent()和detachEvent()。這兩個(gè)方法接受相同的參數(shù):事件名稱和函數(shù)。
在使用這兩組函數(shù)的時(shí)候,先把區(qū)別說(shuō)一下:1.IE不支持捕獲,只支持冒泡;2.IE添加事件不能屏蔽重復(fù)的函數(shù);3.IE中的this指向的是window而不是DOM對(duì)象。4.在傳統(tǒng)事件上,IE是無(wú)法接受到event對(duì)象的,但使用了attchEvent卻可以,但有些區(qū)別。
1.覆蓋問(wèn)題,解決了,但有不同,結(jié)果是Mrs.Lee,Mr.Lee,最后是Lee
window.attachEvent('onload',function(){ alert('Lee'); }); window.attachEvent('onload',function(){ alert('Mr.Lee'); }); window.attachEvent('onload',function(){ alert('Mrs.Lee'); });
2.相同函數(shù)屏蔽的問(wèn)題,未解決。
window.attachEvent('onload',init); window.attachEvent('onload',init); function init(){ alert('Lee'); }
3.是否可以傳遞this,不能,this指的是window。需要用call方法。
window.attachEvent('onload',function(){ var box=document.getElementById('box'); box.attachEvent('onclick',function(){ //alert(this===box); alert(this===window); //true }); });
下面還有辦法就是通過(guò)window.event.srcElement。代碼如下:
window.attachEvent('onload',function(){ var box=document.getElementById('box'); box.attachEvent('onclick',blue); }); function red(){ var that=window.event.srcElement; that.className="red"; that.detachEvent('onclick',red); that.attachEvent('onclick',blue); } function blue(){ var that=window.event.srcElement; that.className="blue"; that.detachEvent('onclick',blue); that.attachEvent('onclick',red); }
4.添加一個(gè)額外的方法,會(huì)不會(huì)被覆蓋,或者只能執(zhí)行一次,解決。
在傳統(tǒng)綁定上,IE是無(wú)法像W3C那樣通過(guò)傳參接受event對(duì)象,但是使用attachEvent()卻可以。
window.attachEvent('onload',function(){ var box=document.getElementById('box'); box.onclick=function(evt){ //傳統(tǒng)方法IE無(wú)法通過(guò)參數(shù)獲取evt alert(evt);//undefined } box.attachEvent('onclick',function(evt){ alert(evt);//object alert(evt.type);//click alert(evt.srcElement.tagName);//DIV alert(window.event.srcElement.tagName);//DIV }); });
跨瀏覽器的兼容
跨瀏覽器添加事件
function addEvent(obj,type,fn){ if(obj.addEventListener){ obj.addEventListener(type,fn,false); }else if(obj.attachEvent){ obj.attachEvent('on'+type,fn); } }
跨瀏覽器移除事件
function removeEvent(obj,type,fn){ if(obj.removeEventListener){ obj.removeEventListener(type,fn,false); }else if(obj.detachEvent){ obj.detachEvent('on'+type,fn); } }
跨瀏覽器獲取目標(biāo)對(duì)象
function getTarget(evt){ if(evt.target){ return evt.target; }else if(window.event.srcElement){ return window.event.srcElement; } }
調(diào)用方式:
addEvent(window,'load',function(){ var box=document.getElementById('box'); addEvent(box,'click',blue); }); function red(evt){ var that=getTarget(evt); that.className="red"; removeEvent(that,'click',red); addEvent(that,'click',blue); } function blue(evt){ var that=getTarget(evt); that.className="blue"; removeEvent(that,'click',blue); addEvent(that,'click',red); }
四.事件對(duì)象的其他補(bǔ)充
relatedTarget事件
w3c中的一個(gè)relatedTarget事件。
例如:
addEvent(window,'load',function(){ var box=document.getElementById('box'); addEvent(box,'mouseover',function(evt){ alert(evt.relatedTarget); //得到移入box最近的那個(gè)DOM對(duì)象 }); addEvent(box,'mouseout',function(evt){ alert(evt.relatedTarget); //從box移出最近的那個(gè)DOM對(duì)象 }); });
IE提供了兩組分別用于移入移出的屬性fromElement和toElement,分別對(duì)應(yīng)mouseover和mouseout。
addEvent(window,'load',function(){ var box=document.getElementById('box'); addEvent(box,'mouseover',function(){ alert(window.event.fromElement.tagName); //得到移入box最近的那個(gè)DOM對(duì)象 }); addEvent(box,'mouseout',function(){ alert(window.event.toElement.tagName); //從box移出最近的那個(gè)DOM對(duì)象 }); });
PS:fromElement和toElement如果分別對(duì)應(yīng)相反的鼠標(biāo)事件,沒(méi)有任何意義。
剩下要做的就是跨瀏覽器兼容操作:
function getTarget(evt){ var e=evt || window.event; if(e.srcElment){ //IE if(e.type=='mouseover'){ return e.fromElement.tagName; }else if(e.type="mouseout"){ return e.toElement.tagName; } }else if(e.relatedTarget){ //w3c return e.relatedTarget; } }
屏蔽跳轉(zhuǎn)操作
取消事件的默認(rèn)行為有一種不規(guī)范的做法,就是返回false。
link.onclick=function(){ alert('Lee'); return false; }
PS:雖然return false;可以實(shí)現(xiàn)這個(gè)功能,但是有漏洞。
第一:必須寫到最后,這樣導(dǎo)致中獎(jiǎng)的代碼執(zhí)行后,有可能執(zhí)行不到return false;
第二:return false 寫到最前那么之后的自定義操作就失效了。
所以最好的辦法應(yīng)該是在最前面就阻止默認(rèn)行為,并且后面的代碼還可以執(zhí)行。
link.onclick=function(evt){ evt.preventDefault;//w3c,阻止默認(rèn)行為 alert('Lee'); } link.onclick=function(evt){ window.event.returnValue=false;//IE,阻止默認(rèn)行為 alert('Lee'); }
那么跨瀏覽器的兼容:
function preDef(evt){ var e=evt || window.event; if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue=false; } }
右鍵菜單contextmenu
兼容:
function preDef(evt){ var e=evt || window.event; if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue=false; } } addEvent(window,"load",function(){ var body=document.getElementsByTagName('body')[0]; addEvent(body,'contextmenu',function(evt){ preDef(evt); }) });
PS:contextmenu事件很常用,這直接導(dǎo)致瀏覽器兼容性較為穩(wěn)定。
卸載前事件:beforeunload
這個(gè)事件可以幫助在離開(kāi)本頁(yè)的時(shí)候給出相應(yīng)的提示,“離開(kāi)”或者“返回”操作。
addEvent(window,'beforeonload',function(){ preDef(evt); });
鼠標(biāo)滾輪(mousewheel)和DOMMouseScroll
用于獲取鼠標(biāo)上下滾輪的距離
addEvent(document,'mousewheel',function(evt){ //非火狐 alert(getWD(evt)); }); addEvent(document,'DOMMouseScroll',function(evt){ //火狐 alert(getWD(evt)); }); function getWD(evt){ var e=evt|| window.event; if(e.wheelDelta){ return e.wheelDelta; }else if(e.detail){ //火狐 return -evt.detail*30; } }
PS:通過(guò)瀏覽器檢測(cè)可以確定火狐只執(zhí)行DOMMouseScroll。
DOMContentLoaded事件和readystatechange事件
DOMContentLoaded事件和readystatechange事件,有關(guān)DOM加載方面的事件。
相關(guān)文章
深入理解JavaScript系列(18):面向?qū)ο缶幊讨瓻CMAScript實(shí)現(xiàn)
這篇文章主要介紹了深入理解JavaScript系列(18):面向?qū)ο缶幊讨瓻CMAScript實(shí)現(xiàn),本文講解了數(shù)據(jù)類型、原始值類型、Object類型、動(dòng)態(tài)性、內(nèi)置對(duì)象、原生對(duì)象及宿主對(duì)象等內(nèi)容,需要的朋友可以參考下2015-03-03關(guān)于javascript中的parseInt使用技巧
前面好幾次遇到JavaScript中parseInt函數(shù)把字串轉(zhuǎn)為數(shù)字,前面遇到過(guò)幾次問(wèn)題,然后就換其它方法進(jìn)行比較。2009-09-09JavaScript 學(xué)習(xí)筆記(十三)Dom創(chuàng)建表格
下面弄個(gè)實(shí)例,運(yùn)用Dom的知識(shí),實(shí)例操作。2010-01-01關(guān)于JavaScript的變量的數(shù)據(jù)類型的判斷方法
這篇文章主要介紹了關(guān)于JavaScript的變量的數(shù)據(jù)類型的判斷方法,JS是一種弱類型語(yǔ)言,其數(shù)據(jù)類型的相關(guān)特性有時(shí)也受到不少開(kāi)發(fā)者的詬病,需要的朋友可以參考下2015-08-08Javascript模塊化編程(一)模塊的寫法最佳實(shí)踐
Javascript模塊化編程,已經(jīng)成為一個(gè)迫切的需求。理想情況下,開(kāi)發(fā)者只需要實(shí)現(xiàn)核心的業(yè)務(wù)邏輯,其他都可以加載別人已經(jīng)寫好的模塊但是,Javascript不是一種模塊化編程語(yǔ)言,它不支持類class,更遑論模塊module了2013-01-01