JavaScript事件---事件綁定和深入


發文不易,轉載傳播,請親注明鏈接出處,謝謝!

內容提綱:

1.傳統事件綁定的問題

2.W3C事件處理函數

3.IE事件處理函數

4.事件對象的其他內容

 

事件綁定分為兩種:一種是傳統事件綁定(內聯模型,腳本模型),一種是現代事件綁定(DOM2級模型)。現代事件綁定在傳統綁定上提供了更強大更方便的功能。

 

一.傳統事件綁定的問題

傳統事件綁定有內聯模型和腳本模型,內聯模型我們不做討論,基本很少去用。先來看一下腳本模型,腳本模型將一個函數賦值給一個事件處理函數。

 

1  var box = document.getElementById('box');        //獲取元素
2 
3        box.onclick = function () {                //元素點擊觸發事件
4 
5               alert('Lee');
6 
7        };

 

 

問題一:一個事件處理函數觸發兩次事件

window.onload = function () {                           //第一組程序項目或第一個JS文件

       alert('Lee');

};

window.onload = function () {                           //第二組程序項目或第二個JS文件

       alert('Mr.Lee');

};

當兩組程序或兩個JS文件同時執行的時候,后面一個會把前面一個完全覆蓋掉導致前面的window.onload完全失效了。

 

解決覆蓋問題,我們可以這樣去解決:

PS如果一開始沒有window.onload,舊版火狐顯示undefined,新版顯示object,谷歌和IE瀏覽器也是object;如果有window.onload,所有瀏覽器都會顯示function。 

 1 window.onload = function () {                           //第一個要執行的事件,會被覆蓋
 2 
 3        alert('Lee');
 4 
 5 };
 6 
 7 if (typeof window.onload == 'function') {             //判斷之前是否有window.onload
 8 
 9        var saved = null;                                //創建一個保存器
10 
11        saved = window.onload;                           //把之前的window.onload保存起來
12 
13 }
14 
15 window.onload = function () {                           //最終一個要執行事件
16 
17        if (saved) saved();                              //執行之前一個事件
18 
19        alert('Mr.Lee');                                 //執行本事件的代碼
20 
21 };

 

問題二:事件切換器

window.onload = function () {

       var box = document.getElementById('box');

       box.onclick =toBlue;                    //第一次執行toBlue()

};

function toRed() {

       this.className = 'red';

       this.onclick = toBlue;                                 //第三次執行toBlue(),然后來回切換

}

PS:直接將函數綁定給事件處理函數,toBlue里面的this就是事件對象box,否則是window!(事件入門一篇提到過)

function toBlue() {

       this.className = 'blue';

       this.onclick = toRed;                                  //第二次執行toRed()

}

 

這個切換器在擴展的時候,會出現一些問題:

1.如果增加一個執行函數,那么會被覆蓋

       box.onclick = toAlert;                                       //被增加的函數

       box.onclick = toBlue;                                      //toAlert被toBlue覆蓋了

 

2.如果解決覆蓋問題,就必須包含到一起,然后同時執行,但又出新問題

      

box.onclick = function () {                                //包含進去,但可讀性降低

 

              //第一次不會被覆蓋,但第二次又被覆蓋(被toBlue里面的onclick覆蓋)

 

     toAlert();                                                 

 

              toBlue.call(this);                                      //還必須把this傳遞到切換器里

 

       };

 

PS:通過匿名函數執行一個函數,此函數里面的this是window,所以必須在匿名函數中把this傳遞過去!

 

綜上的三個問題:覆蓋問題、可讀性問題、this傳遞問題。我們來創建一個自定義的事件處理函數,來解決以上三個問題。

 

//添加事件函數

//obj相當於window

//type相當於onload

//fn相當於function () {}

//組合起來:window.onload = function(){}

 1 function addEvent(obj, type, fn) {                             //取代傳統事件處理函數
 2 
 3        var saved = null;                                       //保存每次觸發的事件處理函數
 4 
 5        if (typeof obj['on' + type] == 'function') {         //判斷是不是事件
 6 
 7               saved = obj['on' + type];                        //如果有,保存起來
 8 
 9        }
10 
11        obj['on' + type] = function () {                  //然后執行window.onload相當於window['onload'];      
12 
13         if (saved) saved();                               //執行上一個 
14 
15               fn.call(this);                               //執行函數,把this傳遞過去
16 
17        };
18 
19 }
20
23 addEvent(window, 'load', function () {                //執行到了
24 
25        alert('Lee');
26 
27 });
28 
29 addEvent(window, 'load', function () {                //執行到了
30 
31        alert('Mr.Lee');
32 
33 });

 

 

PS:以上編寫的自定義事件處理函數,還有一個問題沒有處理,就是兩個相同函數名的函數誤注冊了兩次或多次,那么應該把多余的屏蔽掉。那我們就需要把事件處理函數進行遍歷,如果有同樣名稱的函數名就不添加即可。(這里就不做了)

addEvent(window, 'load', init);                          //注冊第一次

addEvent(window, 'load', init);                          //注冊第二次,應該忽略

function init() {

       alert('Lee');

}

 

用自定義事件函數注冊到切換器上查看效果:

 1 addEvent(window, 'load', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5 addEvent(box, 'click', function () {       //增加一個執行函數,每次都執行,不會被覆蓋
 6 
 7               alert('Mr.Lee');
 8 
 9        });
10 
11        addEvent(box, 'click', toBlue); 12 
13 });
14 
15  
16 
17 function toRed() {
18 
19        this.className = 'red';
20 
21        addEvent(this, 'click', toBlue); 22 
23 }
24 
25  
26 
27 function toBlue() {
28 
29        this.className = 'blue';
30 
31        addEvent(this, 'click', toRed); 32 
33 }

 

PS:當你單擊很多很多次切換后,瀏覽器直接卡死,或者彈出一個錯誤:too much recursion(太多的遞歸)。主要的原因是,每次切換事件的時候,都保存下來,沒有把無用的移除,導致越積越多,最后卡死(解決方案就是用完的事件及時的移除掉)。

//刪除事件函數

1 function removeEvent(obj, type) {
2 
3        if (obj['on'] + type) obj['on' + type] = null;         //刪除事件處理函數
4 
5 }

 

以上的刪除事件處理函數只不過是一刀切的刪除了,這樣雖然解決了卡死和太多遞歸的問題。但其他的事件處理函數也一並被刪除了,導致最后得不到自己想要的結果。如果想要只刪除指定的函數中的事件處理函數,那就需要遍歷,查找。(這里就不做了,提示:在上面的刪除函數中加上第三個參數fn)

 

 

 

二.W3C事件處理函數

“DOM2級事件”定義了兩個方法,用於添加事件和刪除事件處理程序的操作:addEventListener()和removeEventListener()所有DOM節點中都包含這兩個方法,並且它們都接受3個參數;事件名、函數、冒泡或捕獲的布爾值(true表示捕獲,false表示冒泡)

//問題1:覆蓋問題(解決)

 1 window.addEventListener('load', function () {
 2 
 3        alert('Lee');
 4 
 5 }, false);
 6 
 7  
 8 window.addEventListener('load', function () {
 9 
10        alert('Mr.Lee');
11 
12 }, false);

 

//問題2:屏蔽掉相同函數問題(解決)

1 window.addEventListener('load', init, false);        //第一次執行了
2 
3 window.addEventListener('load', init, false);        //第二次被屏蔽了
4 
5 function init() {
6 
7        alert('Lee');
8 
9 }

 

//問題3:是否傳遞了this

 1 window.addEventListener('load', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5        box.addEventListener('click', function(){
 6 
 7             alert(this);     //這兒的this是box
 8     
 9         }, false);
10 
11 },false);            

 

//修改事件切換器

 1 window.addEventListener('load', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5        box.addEventListener('click', toBlue, false);
 6 
 7 },false);
 8 
 9 
10 function toRed() {
11 
12        this.className = 'red';
13 
14        this.removeEventListener('click', toRed, false);
15 
16        this.addEventListener('click', toBlue, false);
17 
18 }
19 
20 
21 function toBlue() {
22 
23        this.className = 'blue';
24 
25        this.removeEventListener('click', toBlue, false);
26 
27        this.addEventListener('click', toRed, false);
28 
29 }

 

//問題4:添加一個額外的方法會被覆蓋或者只能執行一次(解決)

 1 window.addEventListener('load', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5         box.addEventListener('click', function () {          //不會被誤刪
 6 
 7             alert('Mr.Lee');
 8 
 9         }, false);
10 
11        box.addEventListener('click', toBlue, false);        //引入切換也不會因太多遞歸卡死
12 
13 }, false);
14 
15 
16 function toRed() {
17 
18        this.className = 'red';
19 
20        this.removeEventListener('click', toRed, false);
21 
22        this.addEventListener('click', toBlue, false);
23 
24 }
25 
26  
27 function toBlue() {
28 
29        this.className = 'blue';
30 
31        this.removeEventListener('click', toBlue, false);
32 
33        this.addEventListener('click', toRed, false);
34 
35 }        

綜上所述:W3C是比較完美的解決了這些問題,非常好用;但是IE8和之前的瀏覽器並不支持,而是采用了自己的事件,當然,IE9已經完全支持!

 

設置冒泡和捕獲階段

在事件入門一篇中介紹了事件冒泡,即從里到外觸發。我們也可以通過event對象來阻止某一階段的冒泡。

W3C現代事件綁定可以設置冒泡和捕獲。把最后的布爾值設置成true,則為捕獲;設置成false,則為冒泡

//設置為true,捕獲

       document.addEventListener('click', function () {

              alert('document');

       }, true);                                                                 

  box.addEventListener('click', function () {

              alert('div');

       }, true);                                                          

 

 

 

 

三.IE事件處理函數

IE實現了與DOM中類似的兩個方法:attachEvent()和detachEvent()這兩個方法接受相同的參數:事件名稱和函數。

在使用這兩組函數的時候,與W3C區別如下:

1.IE不支持捕獲,只支持冒泡;

2.IE添加事件不能屏蔽重復的函數;

3.IE中的this指向的是window而不是DOM對象;

4.在傳統事件上,IE是無法接受到event對象的,但使用了attchEvent()卻可以,但有些區別。

 

//問題1:覆蓋問題(解決,但有不同,輸出順序相反)

 1 window.attachEvent('onload', function () {
 2 
 3        alert('Lee');
 4 
 5 });
 6 
 7 window.attachEvent('onload', function () {
 8 
 9        alert('Mr.Lee');
10 
11 });
12 
13 window.attachEvent('onload', function () {
14 
15        alert('Miss.Lee');
16 
17 });

 

 

//問題2:相同函數屏蔽的問題(未解決)

 1 window.attachEvent('onload', init);
 2 
 3 window.attachEvent('onload', init);
 4 
 5  
 6 function init() {
 7 
 8        alert('Mr.Lee');
 9 
10 }

 

 

//問題3:不可以傳遞this(未解決)

 1 window.attachEvent('onload', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5        box.attachEvent('onclick', function () {
 6 
 7               //alert(this === box);                   //false
 8 
 9               alert(this === window);                    //不能傳遞this
10 
11        });
12 
13 });
14 
15 //可以通過call()傳遞過去
16 
17 window.attachEvent('onload', function () {
18 
19        var box = document.getElementById('box');
20 
21        box.attachEvent('onclick', function () {       
22 
23               toBlue.call(box);                 //把this直接call過去(但切換器我們不這樣做!)
24 
25        });
26 
27 });
28 
29  

 

//問題4:添加一個額外的方法會被覆蓋或者只能執行一次(解決)

window.attachEvent('onload', function () {

       var box = document.getElementById('box');

       box.attachEvent('onclick', function () {

              alert('Lee');

       });

       box.attachEvent('onclick', function () {

              alert('Mr.Lee');

       });

});

 

在傳統綁定上,IE是無法像W3C那樣通過傳參接受event對象,但如果使用了attachEvent()卻可以。

 1 window.attachEvent('onload', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5        //box.onclick = function (evt) {                     //傳統方法IE無法通過參數獲取evt
 6 
 7        //     alert(evt);
 8 
 9        //}
10 
11        box.attachEvent('onclick', function (evt) {           //IE的現代事件綁定機制是可以的
12 
13               //alert(evt);                               //object
14 
15               //alert(evt.type);                                        //click    
16 
17               //alert(evt.srcElement.tagName);            //box, 這個可以有
18 
19               alert(window.event.srcElement.tagName);    //box, 這個更可以有
20 
21        });
22 
23 });

 

 

//IE事件切換器

 1 window.attachEvent('onload', function () {
 2 
 3        var box = document.getElementById('box');
 4 
 5        box.attachEvent('onclick', toBlue);
 6 
 7 });
 8 
 9  
10 function toRed() {
11 
12        var that = window.event.srcElement;
13 
14        that.className = 'red';
15 
16        that.detachEvent('onclick', toRed);
17 
18        that.attachEvent('onclick', toBlue);
19 
20 }
21 
22  
23 function toBlue() {
24 
25        var that = window.event.srcElement;
26 
27        that.className = 'blue';
28 
29        that.detachEvent('onclick', toBlue);
30 
31        that.attachEvent('onclick', toRed);
32 
33 }

 

最后,為了讓IE和W3C可以兼容這個事件切換器,我們寫成如下方式:

 1 function addEvent(obj, type, fn) {                            //添加事件兼容
 2 
 3        if (obj.addEventListener) {
 4 
 5               obj.addEventListener(type, fn);
 6 
 7        } else if (obj.attachEvent) {
 8 
 9               obj.attachEvent('on' + type, fn);
10 
11        }
12 
13 }
14 
15  
16 
17 function removeEvent(obj, type, fn) {                      //移除事件兼容
18 
19        if (obj.removeEventListener) {
20 
21               obj.removeEventListener(type, fn);
22 
23        } else if (obj.detachEvent) {
24 
25               obj.detachEvent('on' + type, fn);
26 
27        }
28 
29 }
30 
31  
32 
33 function getTarget(evt) {                                       //得到事件目標
34 
35        if (evt.target) {
36 
37               return evt.target;
38 
39        } else if (window.event.srcElement) {
40 
41               return window.event.srcElement;
42 
43        }
44 
45 }
46 
47  
48 
49 addEvent(window, 'load', function () {
50 
51        var box = document.getElementById('box');
52 
53        addEvent(box, 'click', toBlue);
54 
55 });
56 
57  
58 
59 function toRed(evt) {
60 
61        var that = getTarget(evt);
62 
63        that.className = 'red';
64 
65        removeEvent(that, 'click', toRed);
66 
67        addEvent(that, 'click', toBlue);
68 
69 }
70 
71  
72 
73 function toBlue(evt) {
74 
75        var that = getTarget(evt);
76 
77        that.className = 'blue';
78 
79        removeEvent(that, 'click', toBlue);
80 
81        addEvent(that, 'click', toRed);
82 
83 }
84 
85  

 

PS:調用忽略,IE兼容的事件,如果要傳遞this,改成call即可(上面問題3部分說過了,但是一般不使用這種形式)。

PS:IE中的事件綁定函數attachEvent()和detachEvent()可能在實踐中不去使用,有幾個原因:

1.IE9就將全面支持W3C中的事件綁定函數;

2.IE的事件綁定函數無法傳遞this;

3.IE的事件綁定函數不支持捕獲;

4.同一個函數注冊綁定后,沒有屏蔽掉;

5.有內存泄漏的問題。

至於怎么替代,這兒暫時不做探討···

 

 

 

 

四.事件對象的其他內容

1.獲取移入移出對象

W3C提供了一個屬性:relatedTarget;這個屬性可以在mouseover和mouseout事件中獲取從哪里移入和從哪里移出的DOM對象。

       box.onmouseover = function (evt) {                     //鼠標移入box

              alert(evt.relatedTarget);                              //獲取移入box最近的那個元素對象

       }                                                                   

 

  box.onmouseout = function (evt) {                         //鼠標移出box

              alert(evt.relatedTarget);                              //獲取移出box最近的那個元素對象

       }                                                                   

 

IE提供了兩組分別用於移入移出的屬性:fromElement和toElement,分別對應mouseover和mouseout

       box.onmouseover = function (evt) {                   //鼠標移入box

              alert(window.event.fromElement.tagName); //獲取移入box最近的那個元素對象

       }

 

       box.onmouseout = function (evt) {                            //鼠標移入box

              alert(window.event.toElement.tagName);     //獲取移入box最近的那個元素對象

       }

 

跨瀏覽器兼容:

 1 function getTarget(evt) {
 2 
 3        var e = evt || window.event;                        //得到事件對象
 4 
 5        if (e.srcElement) {                                    //如果支持srcElement,表示IE
 6 
 7               if (e.type == 'mouseover') {                   //如果是mouseover
 8 
 9                      return e.fromElement;                   //就使用fromElement
10 
11               } else if (e.type == 'mouseout') {              //如果是mouseout
12 
13                      return e.toElement;                      //就使用toElement
14 
15               }
16 
17        } else if (e.relatedTarget) {                           //如果支持relatedTarget,表示W3C
18 
19               return e.relatedTarget;
20 
21        }
22 
23 }

 

2.阻止默認行為

有時我們需要阻止事件的默認行為,比如:一個超鏈接的默認行為就點擊然后跳轉到指定的頁面。那么阻止默認行為就可以屏蔽跳轉的這種操作,而實現自定義操作。

取消事件默認行為還有一種不規范的做法,就是返回false。

       link.onclick = function () {

              alert('Lee');

      return false;                                              //直接給個false,就不會跳轉了。

       };

 

PS:雖然return false;可以實現這個功能,但有漏洞:

第一:必須寫到最后,這樣導致中間的代碼執行后,有可能執行不到return false;

第二:return false寫到最前那么之后的自定義操作就失效了。

所以,最好的方法應該是在最前面就阻止默認行為,並且后面還能執行代碼。

     

 1   link.onclick = function (evt) {
 2 
 3               evt.preventDefault();                               //W3C阻止默認行為,放哪里都可以
 4 
 5               alert('Mr.Lee');
 6 
 7        };
 8 
 9  
10 
11        link.onclick = function (evt) {
12 
13               window.event.returnValue = false;                  //IE阻止默認行為
14 
15               alert('Mr.Lee');
16 
17        };
18 
19  

 

 

 

跨瀏覽器兼容:

 1 function preDef(evt) {
 2 
 3        var e = evt || window.event;
 4 
 5        if (e.preventDefault) {
 6 
 7               e.preventDefault();
 8 
 9        } else {
10 
11               e.returnValue = false;
12 
13        }
14 
15 } 

 

3.上下文菜單事件

上下文菜單事件:contextmenu,當我們右擊網頁的時候,會自動出現windows自帶的菜單。那么我們可以使用contextmenu事件來修改我們指定的菜單,前提是把右擊的默認行為取消掉。

小示例:

html代碼部分:

 1 <body>
 2 
 3 <textarea id="text" style="width:200px;height:100px;"></textarea>
 4 
 5 <ul id="menu">
 6 
 7               <li>菜單1</li>
 8 
 9               <li>菜單2</li>
10 
11               <li>菜單3</li>
12 
13 </ul>
14 
15 </body>

 

css代碼部分:

 1 #menu {
 2 
 3        width:50px;
 4 
 5        background:grey;
 6 
 7        position:absolute;
 8 
 9        display:none;
10 
11 }

 

JS代碼部分:

addEvent(window, 'load', function () {

       var text = document.getElementById('text');

       addEvent(text, 'contextmenu', function (evt) {

              preDef(evt);

              var menu = document.getElementById('menu');

              var e = evt || window.event;

              menu.style.left = e.clientX + 'px';

              menu.style.top = e.clientY + 'px';

              menu.style.display = 'block';

             

              addEvent(document, 'click', function () {

                     menu.style.display = 'none';

              });

       });

});

 

PS:contextmenu事件很常用,而且此事件各瀏覽器兼容性較為穩定。

 

4.卸載前事件

卸載前事件:beforeunload,這個事件可以幫助在離開本頁的時候給出相應的提示,“離開”或者“返回”操作。

addEvent(window, 'beforeunload', function (evt) {

       preDef(evt);    //必須要有,默認形式

});

 

5.鼠標滾輪事件

鼠標滾輪(mousewheel)和DOMMouseScroll用於獲取鼠標上下滾輪的距離。

 1 addEvent(document, 'mousewheel', function (evt) {          //非火狐
 2 
 3        alert(getWD(evt));
 4 
 5 });
 6 
 7 addEvent(document, 'DOMMouseScroll', function (evt) {  //火狐
 8 
 9        alert(getWD(evt));
10 
11 });
12 
13  
14 
15 function getWD(evt) {
16 
17        var e = evt || window.event;
18 
19        if (e.wheelDelta) {
20 
21               return e.wheelDelta;
22 
23        } else if (e.detail) {
24 
25               return -evt.detail * 30;                        //保持計算的統一
26 
27        }
28 
29 }

 

PS:通過瀏覽器檢測可以確定火狐只執行DOMMouseScroll。

 

6.其他

DOMContentLoaded事件和readystatechange事件(很重要),有關DOM加載方面的事件,關於這兩個事件的內容非常多,這兒就暫時不聊了!

 

for my lover and

thank you Mr.Lee!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM