js中的事件(event)

什么是事件:



我們可以簡單的把事件理解為瀏覽器的感知系統(tǒng)。比如說:他可以感覺到用戶是否點擊(click)了頁面、鼠標是否進入了頁面的某個元素上面(mouseover或mouseenter)、鼠標是否離開了網(wǎng)頁(mouseout或mouseleave)、瀏覽器是都加載完了頁面上的資源(window.onload)、文檔樹是否生成(DOMContentLoaded)、鍵盤上的某個鍵是否按下(keydown)、鼠標的滾輪是否滾動了等等。

? 其實事件的原理并非是瀏覽器的感覺系統(tǒng),它的本質(zhì)是一個行為發(fā)生時,對另一個行為的回調(diào)。

事件的實現(xiàn)(事件綁定):


事件的綁定就是:當這個事件發(fā)生的時候,運行一個或者多個方法(function),比如說當鼠標點擊頁面的時候,就彈出一個“事件”,則寫成:

document.onclick = function(){alert("事件“);}

事件的綁定相當于做計劃,綁定在事件上的方法執(zhí)行了就相當于計劃的事發(fā)生了,所以一般情況下,事件屬性的前面都有”on“,如:ele.onclick, ele.onmousedown, ele.onmouseup, 這里的on,其實就相當于:當什么時候,做計劃要早于計劃的事件發(fā)生。

當然我們也可以不給事件綁定處理方法,也就是說當此事件發(fā)生的時候,什么也不需要做,事件常有,而事件上綁定的方法不一定有,

我們給頁面中的元素的某個事件綁定處理方法的時候。經(jīng)常還會有一個形式參數(shù)e,但是運行的事件,卻沒有辦法傳遞實參給這個形參e,比如:

function fn (e){

//標準瀏覽器中:定義一個形參e,但當事件觸發(fā)的時候,并沒有給e賦實際的值,則瀏覽器會把”事件“的對象賦給這個形參e,這時這個e是個系統(tǒng)級的對象:事件;

IE中的事件對象是個全局的屬性window.event,而標準瀏覽器的事件對象就是形參e;

所以事件對象的兼容性寫法為:e = e||window.event;

以下是常用的事件對象的屬性:

var x =e.clientX,y=e.clientY;所有瀏覽器都支持,相當于瀏覽器中鼠標的坐標;

var x=e.pageX,y = e.pageY;ie8或以下不支持,相當于文檔的中鼠標的坐標;

target事件源;事件源的概念:事件最終發(fā)生在頁面的那個元素上;

事件源和事件的傳播是息息相關(guān)的

事件的傳播包括:冒泡和捕獲;事件傳播是瀏覽器在處理事件行為的機制,冒泡階段或者捕獲階段。 注意:ie 678里,事件傳播只有冒泡階段;

var target = e.target||e.srcElement 后者是處理ie兼容‘;

div1.innerHTML="當前的事件類型:”+e.type+“鼠標的X坐標:“+x+”鼠標的Y坐標:“+Y+”事件發(fā)生在哪兒:“+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元素的默認行為:


? 很多的網(wǎng)頁元素都會有默認的行為,比如說當你點擊一個超鏈接a0標簽的時候,他就會有一個跳轉(zhuǎn)行為;當你在網(wǎng)頁上點擊鼠標右鍵的時候會出現(xiàn)一個右鍵菜單;當你在一個form表單里點擊提交按鈕時網(wǎng)頁會產(chǎn)生一個行為病刷新網(wǎng)頁,當你網(wǎng)頁上滾動鼠標滾輪的時候,頁面的滾動條會滾動等等;這些都叫事件的默認行為,如果想把這些默認行為取消了,相應的js代碼如下:

a.onclick = function(){return false}//方法里加個 return false,就是組織超鏈接點擊時的跳轉(zhuǎn)行為了;

document.oncontextmenu = function(){

//在這里可以加一些代碼,實現(xiàn)自定義的右鍵菜單;

return false //系統(tǒng)自帶的右鍵菜單就失效了

}

Form.onsubmit = function(){return false;}//這樣表單就不會產(chǎn)生提交行為了;

document.onmousewheel = function(){return false;}//IE和chrome的方式,取消鼠標的滾輪的默認行為,網(wǎng)頁的滾動條就不會動了;

document.addEventListener('DOMMouseScoll',function(e){e.preventDefault = true;});火狐的取消滾輪的默認行為;火狐只能用Dom的二級事件綁定方式,并且用e.preventDefault = true;來取消瀏覽器滾輪的默認行為;

我們要知道常見的事件默認行為有哪些,并且要知道阻止默認行為,只要綁定到這個行為事件的方法最后加一句:return false;就可以了;

但是要強調(diào)的是:如果你的事件綁定是用addEventListener來實現(xiàn)的,那阻止默認行為必須用e.preventDefault = true;


事件傳播和阻止事件傳播:


? ? ? 當事件發(fā)生在子元素中的時候,往往會引起連鎖反應,就是在它的祖先元素上也會發(fā)生這個事件,比如說你點擊了一個div,也相當于點擊了一個body,同樣相當于點擊了HTML,同樣相當于點擊了document,

在理解事件傳播的時候要注意兩點:

一、是事件本身在傳播,而不是綁定在事件上的方法在傳播;

二、是并非所有的事件都會傳播,像onfocus,onblur等事件就不會傳播,onmouseenter和onmouseleave事件也不會傳播。

事件委托:


事件委托是利用事件的傳播機制,通過判斷事件源來實現(xiàn)的,是一種高性能的事件處理方式。對事件委托的好處和概念詳見《高程3》的第402頁;

我們通過一個簡單的示例來看看事件的好處。

需求:在如下的HTML代碼中,當你點擊這個頁面中的一個元素時,彈出這個元素對應的標簽名;

<body>

<div id='outer'>

outer

<div id = 'inner'>

inner

<p>ppppp

<span>span

<a href='###'>張松</a>

</span>

</p>

</div>

</div>

</body>

一般的思路是把所有的元素都獲取到,然后循環(huán)綁定,這樣做的缺點就是不僅性能不好,并且還要處理事件傳播的問題,不優(yōu)化的代碼如下;

var eles = document.getElementsByTagNmae('*');

for(var i=0;i<eles.length;i++){

? ?eles,item(i).onclick = function(e){

alert(this.tagName);

e.stopPropagation();//加上阻止事件傳播是可以的,但是性能不是最優(yōu)的;

return false;//阻止超鏈接的默認行為;

}

}


以下用事件委托實現(xiàn)

事件委托:事件委托就是利用事件傳播的機制,無論哪一個頁面元素,他的click事件都會最終傳播到document上;這樣,只需要在document上處理click事件即可;

document.onclick = function(e){

e = e||window.event;

var target = e.target||e.srcElement;//獲得事件源是關(guān)鍵;

alert(target.nodeName);

return false;

};

事件委托的關(guān)鍵是理解號事件的事件源的概念;



DOM二級事件


DOM 是解決文檔里元素關(guān)系的一套模式,其實那只是dom的第一個版本解決的問題。在dom的第二個版本里,解決的問題就不僅僅是文檔里元素之間的關(guān)系了,還把dom元素的事件問題也重新給了一套方案,這套方案就叫做’dom二級事件‘;

DOM二級事件解決了原來的同意事件綁定多個處理方法時,后面綁定的會覆蓋前面綁定的問題,如:

ele.onclick = fn1;

ele.onlcik=fn2;

這樣的處理的結(jié)果就是,ele的onclick事件上,fn2方法把fn1方法給覆蓋了,這樣就不容易實現(xiàn)同一個事件上綁定多個方法。

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的方法卻存在著很多問題,并且非常嚴重。一、被綁定的方法在事件觸發(fā)執(zhí)行時,this關(guān)鍵字竟讓是window,二、IE中被綁定到事件上的方法的執(zhí)行順序是混亂的。

在W3C的標準是在同一事件上,先綁定的方法先執(zhí)行,并且不能重復綁定同一個方法在同一個事件上,但是IE6、7、8中,如果綁定的方法少于9個,執(zhí)行的順序是相反的,超過9個,執(zhí)行順序就是混亂的,這些IE中的問題都是比較嚴重的,我們必須解決好。

事件的兼容性問題總結(jié):(常見的)


* 事件對象本身:標準瀏覽器是事件發(fā)生時自動給方法傳遞一個實參,這個實參就是事件對象,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、執(zhí)行順序是混亂的

DOM二級事件兼容性問題解決之一:解決this關(guān)鍵字


注意:以下代碼中的handler是個形參,他可以表示不同的方法,所以不要把handler認為是某一個具體的方法。如果你bind(ele,'click',fn1),則fn1就是handler,如果bind(ele,'click',fn2),那么fn2就是handler。新手一定要理解好把握好;

關(guān)鍵思路:關(guān)鍵是兩個問題,一、理解好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這個方法了,而是綁定經(jīng)過’化裝‘的fnTemp這個方法

}

或者直接寫:

else if(ele.detachEvent){

? ?ele.detachEvent('on'+type,function(){handler.call(ele)};

}

這樣確實在事件觸發(fā)時,handler運行了,并且讓handler的this指向了被綁定的元素ele,但是由于我已經(jīng)不是直接綁定的handler方法,而是經(jīng)過call變形后的fnTemp方法,那在移除綁定的時候,我們就沒有辦法移除handler方法了。

那我們怎么能找到這個化裝之后的fntemp,并將其移除呢?這件事必須要在綁定事件的時候就要考慮好,在bind方法里,把fnTemp方法保存下來,并且還要用某種方式識別出來這個fnTemp方法是由那個handler’變形。而來的。

這是個不好解釋的難點,做這件事,我們需要兩步;

一、把fnTemp保存下來,不能使用全局變量保存,因為容易被污染,保存在bind的某個變量里,局部變量在unbind這個作用域內(nèi)也訪問不到,在不同的作用域里,還能訪問到一個不是全局變量的值,那用什么呢?最好的方式就是把fnTemp保存在ele這個DOM元素的屬性上,因為這個ele是兩個函數(shù)都要操作的引用類型的變量,那么我們在ele上定義一個自定義屬性,那這個屬性在bind和unbind兩個作用域里面都能訪問到,具體實現(xiàn)方式如下:

else if(ele.attachEvent){

? ?if(!ele['aBind'+type){//如果不存在這個屬性則創(chuàng)建一個

? ? ? ?ele['aBind'+type] = [];//使用數(shù)組來保存被綁定到不同事件上的那些方法(相當?shù)氖录希赡軙唤壎ê芏鄠€handler)

//這個屬性是以‘a(chǎn)Bind"為前綴,以type為區(qū)分符的,Type是事件的類型,這是一個非常重要的技巧

}

var tempFn =function(){handler.call(ele)};讓這個方法運行是this關(guān)鍵字指向被綁定的元素;

ele['aBind'+type].push(tempFn);把變形后的tempFn保存在數(shù)組里

ele.attachEvent('on'+type,tempFn);

}

補充:理解好ele['aBind'+type]=[]這個屬性定義時,’aBind‘這個字符串是區(qū)別符的意思。

先看如果沒有這個區(qū)別符會怎么樣?那就定義了ele[type]=[];如果type是’click‘,則會出現(xiàn)ele.click=[];而ele本身就有click這個方法屬性,我們就沒有辦法修改這個原生的屬性,這樣定義就失效了。所以才給type前面加上'aBind’區(qū)別符作為前綴,以避免或減少和原生的屬性的沖突,這種加前綴的技巧還是很常見。

二、是給tempFn再添加一個自定義屬性,用來標示當前這個tempFn是由handler變形而來的

var tempFn= function(){handler.call(ele);}

tempFn.photo = handler ;//我們通過tempFn的photo這個屬性,就可以分辨出tempFn這個方法是由那個handler變形而來的,photo這個屬性只是在這定義,而使用它是在unbind函數(shù)里。

三、還要強調(diào)一個DOM2級事件綁定變成的原則,即:一個函數(shù)不能重復綁定在同一個事件上。比如:不能把fn1這個函數(shù)重復的綁定給ele的click事件;

ele.addEventListener('click',fn1,false);

ele.addEventListener('click',fn1,false);//綁定兩次或者多次,在事件觸發(fā)時,后面的綁定是無效,

當然,低版本的IE瀏覽器是沒有遵循這個原則的,所以這兒還要解決以下這個問題,加一個判斷即可

for(var i=0;i<ele['aBind'+type].length;i++){

if(ele['aBind'+type][i].photo==handler){//如果數(shù)組中已經(jīng)存在了經(jīng)過化妝的handler方法,則退出循環(huán);

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,該做的準備工作,都已經(jīng)在bind里完成了。unbind負責把綁定的在事件上的方法移除,但現(xiàn)在已經(jīng)不是移除handler這個方法了,而是移除經(jīng)過化裝后的這個方法。這個方法保存在ele的['aBind'+type這個屬性上。

我們知道ele['aBind'+type]這是個數(shù)組,先把它取到。賦值給一個短變量a:var a=ele['aBind'+type]。操作a這個短變量比操作ele['aBind'+type]這個屬性更方便。

然后遍歷這個數(shù)組,逐個比較那個是經(jīng)過化裝的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);//移除時間之后一定要把這個方法從數(shù)組里移除了,要不然下一次就不能再綁定了。

return;//以為每一次綁定都是唯一的(bind上講到過),所以移除后直接結(jié)束這個函數(shù)的運行就可以了;

}

}

因為用splice會造成“數(shù)組塌陷”,所以在正式的代碼里,用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;

}

}

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容