事件綁定分為兩種:
一種是傳統事件綁定(內聯模型/腳本模型);上一章內容;
一種是現代事件綁定(DOM2級模型);現代事件綁定在傳統事件綁定基礎上提供了更強大的功能;
一 傳統事件綁定的問題
// 腳本模型將一個函數賦值給一個事件處理函數; var box = document.getElementById('box'); // 獲取元素; box.onclick = function(){ // 元素點擊觸發事件; alert('Lee'); } // 問題一:一個事件處理函數觸發兩次事件; window.onload = function(){ // 第一組程序; alert('Lee'); } window.onload = function(){ // 第二組程序; alert('Mr.Lee'); } // PS:當兩組程序同時執行的時候,後面一個會把前面一個完全覆蓋; // 導致前面的window.onload完全失效了; // 解決方案: window.onload = function(){ // 第一組事件處理程序,會被覆蓋; alert('Lee'); } if(typeof window.onload == 'function'){ // 判斷之前是否有window.onload; var saved = null; // 創建一個保存器; saved = window.onload; // 把之前的window.onload保存起來; } window.onload = function(){ // 下一個要執行的事件; // saved()=window.onload = function if(saved)saved(); // 判斷之前是否有事件,如果有則先執行之前保存的事件; alert('Mr.Lee'); // 執行本事件的代碼; }
// 問題二:事件切換器 box.onclick = boBlue; // 第一次執行toBlue(); function toRed(){ this.className = 'red'; this.onclick = toBlue; // 第三次執行roBlue(),然後來回切換; } function toBlue(){ this.className = 'blue'; this.onclick = toRed; // 第二次執行toRed(); } // 這個切換器在擴展的時候,會出現一些問題: 1.如果增加一個執行函數,那麼會被覆蓋; box.onclick = toAlert; // 被增加的函數; box.onclick = toBlue; // toAlert被覆蓋了; 2.如果解決覆蓋問題,就必須包含同時執行; box.onclick = function(){ // 包含進去,但可讀性降低; toAlert(); // 第一次不會被覆蓋,但第二次又被覆蓋; toBlue.call(this); // 還必須把this傳遞到切換器裡; }
// 綜上三個問題:覆蓋問題/可讀性問題/this傳遞為題; // 我們創建一個自定義事件處理函數; function addEvent(obj,type,fn){ // 取代傳統事件處理函數; var saved = null; // 保存每次觸發的事件處理函數; if(typeof obj['on'+type] == 'function'){// 判斷是不是存在事件; saved = obj['on'+type]; // 如果有,保存起來; } obj['on'+type] = function(){ // 然後執行; if(saved)saved(); // 執行上一個; fn.call(this); // 執行函數,把this傳遞進去; } } addEvent(window,'load',function(){ alert('Lee'); // 可以執行; }); addEvent(window.'load',function(){ alert('Mr.Lee'); // 可以執行; }) // 用自定義事件函數注冊到切換器上查看效果: addEvent(window,'load',function(){ var box = document.getElementById('box'); addEvent(box,'click',toBlue); }); function toRed(){ this.className = 'red'; addEvent(this,'click',toBlue); } function toBlue(){ this.className = 'blue'; addEvent(this,'click',toRed);
二 W3C事件處理函數
// "DOM2級事件"定義了兩個方法,用於添加事件和刪除事件的處理程序:addEventListener()和removeEventListener();
// 所有DOM節點中都包含這兩個方法,並且它們都接收3個參數:事件名/函數/冒泡或捕獲的布爾值(true表示捕獲,false表示冒泡); window.addEventListener('load',function(){ alert('Lee'); },false); window.addEventListener('load',function(){ alert('Mr.Lee'); },false); // PS:W3C的事件綁定好處:1.不需要自定義了;2.可以屏蔽相同的函數;3.可以設置冒泡和捕獲; window.addEventListener('load',init,false); // 第一次執行了; window.addEventListener('load',init,false); // 第二次被屏蔽了; function init(){ alert('Lee'); } // 事件切換器 window.addEventListener('load',function(){ var box = document.getElementById('box'); box.addEventListener('click',function(){ // 不會被覆蓋/誤刪; alert('Lee'); },false); box.addEventListener('click',toBlue,false); // 引入切換; },false); function toRed(){ this.className = 'red'; this.removeEventListener('click',toRed,false); // 移除事件處理函數; this.addEventListener('click',toBlue,false); // 添加需要切換的事件處理函數; } function toBlue(){ this.className = 'blue'; this.removeEventListener('click',toBlue,false); this.addEventListener('click',toRed,false); } // 設置冒泡和捕獲階段 document.addEventListener('click',function(){ alert('document'); },true); // 設置為捕獲; document.addEventListener('click',function(){ alert('Lee'); },false); // 設置為冒泡;
三 IE事件處理函數
// IE中實現了與DOM中類似的兩個方法:attachEvent()和detachEvent();
// 這兩個方法接收相同的參數:事件名和函數;
// 在使用這兩組函數的時候,區別: // 1.IE不支持捕獲,只支持冒泡; // 2.IE添加事件不能屏蔽重復的函數; // 3.IE中的this指向的是window而不是DOM對象; // 4.在傳統事件上,IE是無法接受到event對象的;但使用了attachEvent()卻可以; window.attachEvent('onload',function(){ var box = document.getElementById('box'); box.attachEvent('onclick',toBlue); }); function toRed(){ var that = window.event.srcElement; that.className = 'red'; that.detachEvent('onclick',toRed); that.attachEvent('onclick',toBlue); } function toBlue(){ var that = window.event.srcElement; that.className = 'blue'; that.detachEvent('onclick',toBlue); that.attachEvent('onclick',toRed); } // PS:IE不支持捕獲; // IE不能屏蔽; // IE不能傳遞this,可以call過去; // 在傳統綁定上,IE是無法像W3C那樣通過傳參接受event對象;但如果使用了attachEvent()卻可以; box.onclick = function(evt){ alert(evt); // undefined; } box.attachEvent('onclick',function(evt){ alert(evt); // object; alert(evt.type); // click; }); // 兼容IE和W3C的事件切換器函數; function addEvent(obj,type,fn){ // 添加事件處理程序兼容; if(obj.addEventListener){ obj.addEventListener(type,fn); }else if(obj.attachEvent){ obj.attachEvent('on'+type,fn); } } function removeEvent(obj,type,fn){ // 移除事件處理程序兼容; if(obj.removeEventListener){ obj.removeEventListener(type,fn); }esle if(obj.detachEvent){ obj.detachEvent('on'+type,fn); } } function getTarget(evt){ // 得到事件目標; if(evt.target){ return evt.target; }else if(window.event.srcEleemnt){ return window.event.srcElement; } }
四 事件對象補充
1.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; // 兼容函數 function getEarget(evt){ var e = evt || window.event; // 得到事件對象; if(e.srcElement){ // 如果支持srcElement,表示IE; if(e.type == 'mouseover'){ // 如果是over事件; return e.fromeElement; // 就使用from; }else if(e.type == 'mouseout'){ // 如果是out; return e.toElement; // 就使用to; } }else if(e.relatedTarget){ // 如果支持relatedTarget,表示W3C; return e.relatedTarget; } }
2.阻止事件的默認行為
// 一個超鏈接的默認行為就點擊然後跳轉到指定的頁面; // 那麼阻止默認行為就可以屏蔽跳轉的這種操作,而實現自定義操作; // 取消事件默認行為還有一種不規范的做法,就是返回false; link.onclick = function(){ alert('Lee'); return false; // 直接返回false,就不會跳轉了; } // PS:雖然return false;可以實現這個功能,但有漏洞; // 第一:代碼必須寫到最後,這樣導致中間的代碼執行後,有可能執行不到return false; // 第二:return false寫到最前那麼之後的自定義操作就失敗了; // 解決方案:在最前面就阻止默認行為,並且後面還能執行代碼; function preDef(evt){ // 跨浏覽器兼容阻止默認行為; var e = evt || window.event; if(e.preventDefault){ e.preventDefault(); // W3C,阻止默認行為; }else{ e.returnValue = false; // IE,阻止默認行為; } }
3.上下文菜單事件contextmenu // 當我們右擊網頁的時候,會自動出現windows自帶的菜單; // 那麼我們可以使用contextmenu事件來修改我們指定的菜單;但前提是把右擊的默認行為取消; addEvent(window,'load',function(){ var text = docuemnt.getElementById('text'); addEvent(text,'contextmenu',function(evt){ // 添加右鍵菜單事件處理程序; var e = evt || window.event; preDef(e); // 阻止默認行為函數; var menu = document.getElementById('menu'); // 找到自定義的menu對象; menu.style.left = e.clientX+'px'; // 確定自定義menu在屏幕上的位置; menu.style.top = e.clientX+'px'; menu.style.visibility = 'visible'; // 設置自定義menu的屬性為可見; addEvent(document,'click',function(){ // 給document添加單擊事件處理程序; docuemnt.getElementById('myMenu').style.visibility = 'hidden'; //將自定義的menu隱藏; }); }); });
4.卸載前事件beforeunload
// 這個事件可以幫助在離開本頁的時候給出相應的提示;"離開"或"返回"操作; addEvent(window.'beforeunload',function(evt){ var evt = event || window.event; var message = '是否離開此頁?'; evt.returnValue = message; return message; });
5.鼠標滾輪(mousewheel)和DOMMouseScroll
// 用於獲取鼠標上下滾輪的距離; addEvent(docuemnt,'mousewheel',function(evt){ // 非Firefox; alert(getWD(evt)); }); addEvent(docuemnt,'DOMMouseScroll',function(evt){ // Firefox; alert(getWD(evt)); }); function getWD(evt){ var e = evt || window.event; if(e.wheelDelta){ // mousewheel事件的滾動值保存在wheelDelta裡; return e.wheelDelta; }else if(e.detail){ // DOMMouseScroll事件的滾動值保存在detail裡; return -evt.detail*30; // 保持計算的統一; } }