發文不易,轉載傳播,請親注明鏈接出處,謝謝!
內容提綱:
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!