JavaScript的事件綁定及深刻

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


一.傳統事件綁定的問題
傳統事件綁定有內聯模型和腳本模型,內聯模型咱們不作討論,基本不多去用。先來看
一下腳本模型,腳本模型將一個函數賦值給一個事件處理函數。瀏覽器

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

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

  
  
           
  
  
  1. window.onload = function () { //第一組程序項目或第一個JS 文件 
  2. alert('Lee'); 
  3. }; 
  4.  
  5. window.onload = function () { //第二組程序項目或第二個JS 文件 
  6. alert('Mr.Lee'); 
  7. }; 

當兩組程序或兩個JS 文件同時執行的時候,後面一個會把前面一個徹底覆蓋掉。致使
前面的window.onload 徹底失效了。函數


解決覆蓋問題,咱們能夠這樣去解決:this

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

問題二:事件切換器spa

  
  
           
  
  
  1. box.onclick = toBlue; //第一次執行boBlue() 
  2. function toRed() { 
  3. this.className = 'red'
  4. this.onclick = toBlue; //第三次執行toBlue(),而後來回切換 
  5. function toBlue() { 
  6. this.className = 'blue'
  7. this.onclick = toRed; //第二次執行toRed() 

這個切換器在擴展的時候,會出現一些問題:
1.若是增長一個執行函數,那麼會被覆蓋對象

  
  
           
  
  
  1. box.onclick = toAlert; //被增長的函數 
  2. box.onclick = toBlue; //toAlert 被覆蓋了 

2.若是解決覆蓋問題,就必須包含同時執行,但又出新問題遞歸

  
  
           
  
  
  1. box.onclick = function () { //包含進去,但可讀性下降 
  2. toAlert(); //第一次不會被覆蓋,但第二次又被覆蓋 
  3. toBlue.call(this); //還必須把this 傳遞到切換器裏 
  4. }; 

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

  
  
           
  
  
  1. function addEvent(obj, type, fn) { //取代傳統事件處理函數 
  2. var saved = null//保存每次觸發的事件處理函數 
  3. if (typeof obj['on' + type] == 'function') { //判斷是否是事件 
  4. saved = obj['on' + type]; //若是有,保存起來 
  5. obj['on' + type] = function () { //而後執行 
  6. if (saved) saved(); //執行上一個 
  7. fn.call(this); //執行函數,把this 傳遞過去 
  8. }; 
  9. addEvent(window, 'load'function () { //執行到了 
  10. alert('Lee'); 
  11. }); 
  12. addEvent(window, 'load'function () { //執行到了 
  13. alert('Mr.Lee'); 
  14. }); 

PS:以上編寫的自定義事件處理函數,還有一個問題沒有處理,就是兩個相同函數名
的函數誤註冊了兩次或屢次,那麼應該把多餘的屏蔽掉。那,咱們就須要把事件處理函數進
行遍歷,若是有一樣名稱的函數名就不添加便可。(這裏就不作了)事件

  
  
           
  
  
  1. addEvent(window, 'load', init); //註冊第一次 
  2. addEvent(window, 'load', init); //註冊第二次,應該忽略 
  3. function init() { 
  4. alert('Lee'); 

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

  
  
           
  
  
  1. addEvent(window, 'load'function () { 
  2. var box = document.getElementById('box'); 
  3. addEvent(box, 'click', toBlue); 
  4. }); 
  5. function toRed() { 
  6. this.className = 'red'
  7. addEvent(this'click', toBlue); 
  8. function toBlue() { 
  9. this.className = 'blue'
  10. addEvent(this'click', toRed); 

PS:當你單擊不少不少次切換後,瀏覽器直接卡死,或者彈出一個錯誤:too much
recursion(太多的遞歸)。主要的緣由是,每次切換事件的時候,都保存下來,沒有把無用的
移除,致使越積越多,最後卡死。

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

以上的刪除事件處理函數只不過是一刀切的刪除了,這樣雖然解決了卡死和太多遞歸的
問題。但其餘的事件處理函數也一併被刪除了,致使最後得不到本身想要的結果。若是想要
只刪除指定的函數中的事件處理函數,那就須要遍歷,查找。(這裏就不作了)


二.W3C事件處理函數
「DOM2 級事件」定義了兩個方法,用於添加事件和刪除事件處理程序的操做:
addEventListener()和removeEventListener()。全部DOM 節點中都包含這兩個方法,而且它
們都接受3 個參數;事件名、函數、冒泡或捕獲的布爾值(true 表示捕獲,false 表示冒泡)。

  
  
           
  
  
  1. window.addEventListener('load'function () { 
  2. alert('Lee'); 
  3. }, false); 
  4. window.addEventListener('load'function () { 
  5. alert('Mr.Lee'); 
  6. }, false); 

PS:W3C 的現代事件綁定比咱們自定義的好處就是:1.不須要自定義了;2.能夠屏蔽相
同的函數;3.能夠設置冒泡和捕獲。

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

事件切換器

  
  
           
  
  
  1. window.addEventListener('load'function () { 
  2. var box = document.getElementById('box'); 
  3. box.addEventListener('click'function () { //不會被誤刪 
  4. alert('Lee'); 
  5. }, false); 
  6. box.addEventListener('click', toBlue, false); //引入切換也不會太多遞歸卡死 
  7. }, false); 
  8. function toRed() { 
  9. this.className = 'red'
  10. this.removeEventListener('click', toRed, false); 
  11. this.addEventListener('click', toBlue, false); 
  12. function toBlue() { 
  13. this.className = 'blue'
  14. this.removeEventListener('click', toBlue, false); 
  15. this.addEventListener('click', toRed, false); 

設置冒泡和捕獲階段
以前咱們上一章瞭解了事件冒泡,即從裏到外觸發。咱們也能夠經過event 對象來阻止
某一階段的冒泡。那麼W3C 現代事件綁定能夠設置冒泡和捕獲。

  
  
           
  
  
  1. document.addEventListener('click'function () { 
  2. alert('document'); 
  3. }, true); //把布爾值設置成true,則爲捕獲 
  4. box.addEventListener('click'function () { 
  5. alert('Lee'); 
  6. }, true); //把布爾值設置成false,則爲冒泡 

三.IE事件處理函數
IE 實現了與DOM 中相似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受
相同的參數:事件名稱和函數。
在使用這兩組函數的時候,先把區別說一下:1.IE 不支持捕獲,只支持冒泡;2.IE 添加
事件不能屏蔽重複的函數;3.IE 中的this 指向的是window 而不是DOM 對象。4.在傳統事
件上,IE 是沒法接受到event 對象的,但使用了attchEvent()卻能夠,但有些區別。

  
  
           
  
  
  1. window.attachEvent('onload'function () { 
  2. var box = document.getElementById('box'); 
  3. box.attachEvent('onclick', toBlue); 
  4. }); 
  5. function toRed() { 
  6. var that = window.event.srcElement; 
  7. that.className = 'red'
  8. that.detachEvent('onclick', toRed); 
  9. that.attachEvent('onclick', toBlue); 
  10. function toBlue() { 
  11. var that = window.event.srcElement; 
  12. that.className = 'blue'
  13. that.detachEvent('onclick', toBlue); 
  14. that.attachEvent('onclick', toRed); 

PS:IE 不支持捕獲,無解。IE 不能屏蔽,須要單獨擴展或者自定義事件處理。IE 不能
傳遞this,能夠call 過去。

  
  
           
  
  
  1. window.attachEvent('onload'function () { 
  2. var box = document.getElementById('box'); 
  3. box.attachEvent('onclick'function () { 
  4. alert(this === window); //this 指向的window 
  5. }); 
  6. }); 
  7. window.attachEvent('onload'function () { 
  8. var box = document.getElementById('box'); 
  9. box.attachEvent('onclick'function () { 
  10. toBlue.call(box); //把this 直接call 過去 
  11. }); 
  12. }); 
  13. function toThis() { 
  14. alert(this.tagName); 

在傳統綁定上,IE 是沒法像W3C 那樣經過傳參接受event 對象,但若是使用了
attachEvent()卻能夠。

  
  
           
  
  
  1. box.onclick = function (evt) { 
  2. alert(evt); //undefined 
  3. box.attachEvent('onclick'function (evt) { 
  4. alert(evt); //object 
  5. alert(evt.type); //click 
  6. }); 
  7. box.attachEvent('onclick'function (evt) { 
  8. alert(evt.srcElement === box); //true 
  9. alert(window.event.srcElement === box); //true 
  10. }); 

最後,爲了讓IE 和W3C 能夠兼容這個事件切換器,咱們能夠寫成以下方式:

  
  
           
  
  
  1. function addEvent(obj, type, fn) { //添加事件兼容 
  2. if (obj.addEventListener) { 
  3. obj.addEventListener(type, fn); 
  4. else if (obj.attachEvent) { 
  5. obj.attachEvent('on' + type, fn); 
  6. function removeEvent(obj, type, fn) { //移除事件兼容 
  7. if (obj.removeEventListener) { 
  8. obj.removeEventListener(type, fn); 
  9. else if (obj.detachEvent) { 
  10. obj.detachEvent('on' + type, fn); 
  11. function getTarget(evt) { //獲得事件目標 
  12. if (evt.target) { 
  13. return evt.target; 
  14. else if (window.event.srcElement) { 
  15. return window.event.srcElement; 

PS:調用忽略,IE 兼容的事件,若是要傳遞this,改爲call 便可。
PS:IE 中的事件綁定函數attachEvent()和detachEvent()可能在實踐中不去使用,有幾個
緣由:1.IE9 就將全面支持W3C 中的事件綁定函數;2.IE 的事件綁定函數沒法傳遞this;3.IE
的事件綁定函數不支持捕獲;4.同一個函數註冊綁定後,沒有屏蔽掉;5.有內存泄漏的問題。
至於怎麼替代,咱們將在之後的項目課程中探討。


四.事件對象的其餘補充
在W3C 提供了一個屬性:relatedTarget;這個屬性能夠在mouseover 和mouseout 事件
中獲取從哪裏移入和從哪裏移出的DOM 對象。

  
  
           
  
  
  1. box.onmouseover = function (evt) { //鼠標移入box 
  2. alert(evt.relatedTarget); //獲取移入box 最近的那個元素對象 
  3. //span 
  4. box.onmouseout = function (evt) { //鼠標移出box 
  5. alert(evt.relatedTarget); //獲取移出box 最近的那個元素對象 
  6. //span 

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

  
  
           
  
  
  1. box.onmouseover = function (evt) { //鼠標移入box 
  2. alert(window.event.fromElement.tagName); //獲取移入box 最近的那個元素對象span 
  3. box.onmouseout = function (evt) { //鼠標移入box 
  4. alert(window.event.toElement.tagName); //獲取移入box 最近的那個元素對象span 

PS:fromElement 和toElement 若是分別對應相反的鼠標事件,沒有任何意義。


剩下要作的就是跨瀏覽器兼容操做:

  
  
           
  
  
  1. function getTarget(evt) { 
  2. var e = evt || window.event; //獲得事件對象 
  3. if (e.srcElement) { //若是支持srcElement,表示IE 
  4. if (e.type == 'mouseover') { //若是是over 
  5. return e.fromElement; //就使用from 
  6. else if (e.type == 'mouseout') { //若是是out 
  7. return e.toElement; //就使用to 
  8. else if (e.relatedTarget) { //若是支持relatedTarget,表示W3C 
  9. return e.relatedTarget; 

有時咱們須要阻止事件的默認行爲,好比:一個超連接的默認行爲就點擊而後跳轉到指
定的頁面。那麼阻止默認行爲就能夠屏蔽跳轉的這種操做,而實現自定義操做。
取消事件默認行爲還有一種不規範的作法,就是返回false。

  
  
           
  
  
  1. link.onclick = function () { 
  2. alert('Lee'); 
  3. return false//直接給個假,就不會跳轉了。 
  4. }; 

PS:雖然return false;能夠實現這個功能,但有漏洞;第一:必須寫到最後,這樣致使
中間的代碼執行後,有可能執行不到return false;第二:return false 寫到最前那麼以後的自
定義操做就失效了。因此,最好的方法應該是在最前面就阻止默認行爲,而且後面還能執行
代碼。

  
  
           
  
  
  1. link.onclick = function (evt) { 
  2. evt.preventDefault(); //W3C,阻止默認行爲,放哪裏均可以 
  3. alert('Lee'); 
  4. }; 
  5. link.onclick = function (evt) { //IE,阻止默認行爲 
  6. window.event.returnValue = false
  7. alert('Lee'); 
  8. }; 

跨瀏覽器兼容

  
  
           
  
  
  1. function preDef(evt) { 
  2. var e = evt || window.event; 
  3. if (e.preventDefault) { 
  4. e.preventDefault(); 
  5. else { 
  6. e.returnValue = false

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

  
  
           
  
  
  1. addEvent(window, 'load'function () { 
  2. var text = document.getElementById('text'); 
  3. addEvent(text, 'contextmenu'function (evt) { 
  4. var e = evt || window.event; 
  5. preDef(e); 
  6. var menu = document.getElementById('menu'); 
  7. menu.style.left = e.clientX + 'px'
  8. menu.style.top = e.clientY + 'px'
  9. menu.style.visibility = 'visible'
  10. addEvent(document, 'click'function () { 
  11. document.getElementById('myMenu').style.visibility = 'hidden'
  12. }); 
  13. }); 
  14. }); 

PS:contextmenu 事件很經常使用,這直接致使瀏覽器兼容性較爲穩定。


卸載前事件:beforeunload,這個事件能夠幫助在離開本頁的時候給出相應的提示,「離
開」或者「返回」操做。

  
  
           
  
  
  1. addEvent(window, 'beforeunload'function (evt) { 
  2. preDef(evt); 
  3. }); 

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

  
  
           
  
  
  1. addEvent(document, 'mousewheel'function (evt) { //非火狐 
  2. alert(getWD(evt)); 
  3. }); 
  4. addEvent(document, 'DOMMouseScroll'function (evt) { //火狐 
  5. alert(getWD(evt)); 
  6. }); 
  7. function getWD(evt) { 
  8. var e = evt || window.event; 
  9. if (e.wheelDelta) { 
  10. return e.wheelDelta; 
  11. else if (e.detail) { 
  12. return -evt.detail * 30; //保持計算的統一 

PS:經過瀏覽器檢測能夠肯定火狐只執行DOMMouseScroll。

DOMContentLoaded 事件和readystatechange 事件,有關DOM 加載方面的事件,關於這 兩個事件的內容很是多且繁雜,咱們先點明在這裏,在項目課程中使用的時候詳細討論。

相關文章
相關標籤/搜索