js中的事件(event)

什么是事件:



我們可以簡單的把事件理解為瀏覽器的感知系統。比如說:他可以感覺到用戶是否點擊(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;

}

}

}

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容