歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

JavaScript高級程序設計筆記 事件冒泡和事件捕獲

1、事件冒泡


要理解事件冒泡,就得先知道事件流。事件流描述的是從頁面接收事件的順序,比如如下的代碼:

<body>
  <div> 
    click me!
  </div>
</body>

如果在body和div內都注冊了click的事件監聽,之後又點擊了div區域,是body先響應還是div先響應?有意思的是,當時的浏覽器開發團隊IE和Netscape提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape提出的事件流是事件捕獲流。

IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點(文檔)。如上代碼,點擊click事件會這樣傳播:div->body->html->document(雖然我沒寫html元素,但是頁面上默認還是會存在的)

現代的所有浏覽器都支持事件冒泡,但還是有些細微差別。IE5.5以及更早版本中的事件冒泡會跳過<html>元素(從body直接跳到document)。IE9、ff、chrome和safari則將事件一直冒泡到window對象。

2、事件捕獲


Netscape團隊則提出另一種事件流-事件捕獲。事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件,如果仍以上面的代碼舉例:document->html->body->div。

雖然事件捕獲是Netscape唯一支持的事件流模型,但是IE9、Safari、chrome、opera和ff目前也都支持這種事件流模型。盡管“DOM2級事件”規范要求事件應該從document對象開始傳播,但這些浏覽器都是從window對象開始捕獲事件的。

因為老版本的浏覽器不支持事件捕獲,所以我們建議使用事件冒泡。

3、DOM事件流


“DOM2級事件”規定事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。還是上面的代碼作為例子,單擊div元素會按照如下順序觸發事件:document->html->body->div->body->html->document。

在DOM事件流中,實際的目標(div)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件到body就停止了,下一個階段是“處於目標”階段,於是事件在div上發生,並在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播回文檔。但是多數支持DOM事件流的浏覽器都實現了一種特定的行為:即使“DOM2級事件”規范明確要求捕獲階段不會涉及目標事件,但IE9、safari、chrome、ff和opera9.5及更高版本都會在捕獲階段觸發事件對象上的事件,結果就是有兩個機會在目標對象上面操作。(IE9、opera、ff、chrome和Safari都支持DOM事件流,IE8及更早版本不支持DOM事件流)。

4、事件處理程序


響應某個事件的函數就叫做事件處理程序

DOM0級的事件處理程序很簡單,onclick就是常用的DOM0級事件處理函數,只會在冒泡階段被處理。

而“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程序的操作:addEventListener()removeEventListener(),所有DOM節點都包含這兩個方法,並且它們都接受3個參數:要處理的事件名、作為事件處理程序的函數和一個布爾值。最後這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用。DOM2級方法添加事件處理程序的好處是可以添加多個事件處理程序,會按照添加順序被處理(無論是捕獲還是冒泡)。這也是為什麼DOM0級事件兼容各種浏覽器,我們卻還是要使用DOM2的原因之一。

var div = document.getElementById('myDiv');
div.addEventListener('click', function() {
  console.log(this.id);
}, true);

div.addEventListener('click', function() {
  console.log('hello world');
}, true);

而IE與DOM不同,它有自己的方法:attachEvent()detachEvent(),這兩個方法接受相同的兩個參數:事件處理程序名稱和事件處理程序函數。由於IE8以及更早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程序都會被添加到冒泡階段(所以不需要第三個參數)。

var div = document.getElementById('myDiv');
div.attachEvent('onclick', function() {
  console.log('hello world');
});

注意第一個參數是onclick,而非DOM標准的click。在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程序的作用域,在使用DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行,而在使用attachEvent()方法的情況下,事件處理程序在全局作用域中運行,因此this等於window(這點要特別注意!!!)。attachEvent()也能添加多個事件處理程序,但是事件的執行順序和添加順序相反

5、跨浏覽器的事件處理程序


因為浏覽器之間的差異(其實就是IE大家都懂的),所以需要編寫跨浏覽器的事件處理程序。

var EventUtil = {
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {  // DOM2
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {  // IE
      element.attachEvent('on' + type, handler);
    } else {  // DOM0
      element['on' + type] = handler;
    }
  },

  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent('on' + type, handler);
    } else {
      element['on' + type] = null;
    }
  }
};

6、事件對象


在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象包含著所有與事件有關的信息。坑爹的是DOM中的事件對象和IE又有不同的玩法。

先來說說DOM中的:

var div = document.getElementById('myDiv');
div.onclick = function(e) {
  console.log(e.type);
};

div.addEventListener('click', function(e) {
  console.log(e.type);
}, false);

上面代碼我們應該都不陌生,分別實現了DOM0級和DOM2級的事件對象。

e有很多的屬性和方法,這裡提幾個常用的。targetcurrentTarget,target指的是事件的真正目標,而currentTarget指的是當前的目標,正是利用target我們可以做事件代理

要阻止特定事件的默認行為,我們可以使用preventDefault()方法,例如鏈接的默認行為就是在被單擊時會導航到其href指定的url,如果你想阻止這個默認行為,那麼通過鏈接的onclick事件處理程序可以取消它:

var link = document.getElementById('myLink');
link.onclick = function(e) {
  e.preventDefault();
};

只有cancelable屬性設置為true的事件,才可以使用preventDefault()來取消其默認行為。

另外,stopPropagation()方法用於立即停止事件在DOM層中的傳播,即取消進一步的事件捕獲或冒泡。

var div = document.getElementById('myDiv');
div.onclick = function(e) {
  console.log('click!');
  e.stopPropagation();
};

document.body.onclick = function(e) {
  console.log('hello world');
};

而IE中的事件對象是這麼用的:

var div = document.getElementById('myDiv');
div.onclick = function() {
  var e = window.e;
  console.log(e.type);
};

div.attachEvent('onclick', function(e) {
  // 也可以通過window.e訪問
  console.log(e.type);
});

IE中的event對象也有很多屬性和方法,比如srcElement就是和DOM中的target屬性相同,而returnValue屬性相當於DOM中的preventDefault()方法,它們的作用都是取消給定事件的默認行為。只要將該值設置為false,就可以阻止默認行為。相應地,canceBubble屬性和DOM中的stopPropagation()方法作用相同,因為IE只支持冒泡,所以它只能取消事件冒泡。

跨浏覽器的事件對象:

var EventUtil = {
  getEvent: function(e) {
    return e ? e : window.e;
  },

  getTarget: function(e) {
    return e.target || e.srcElement;
  },

  preventDefault: function(e) {
    if (e.preventDefault) {
      e.preventDefault();
    } else {
      e.returnValue = false;
    }
  },

  stopPropagation: function(e) {
    if (e.stopPropagation) {
      e.stopPropagation()
    } else {
      e.cancelBubble = true;
    }
  }
}

7、事件委托


有了以上作為基礎,事件委托應該是很簡單了。什麼是事件委托?對“事件處理程序過多”問題的解決方案就是事件委托。事件委托利用了事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。例如,click事件會冒泡到document層次,也就是說,我們可以為整個頁面指定一個onclick事件處理程序,而不必給每個可單擊的元素分別添加事件處理程序。

舉個經常舉的例子,比如有如下代碼:

<ul id='myLink'>
  <li id='a'> apple </li>
  <li id='b'> banana </li>
  <li id='c'> orange </li>
</ul>

需要的效果是每點擊相應的<li>選項,alert它裡面的單詞,或許很簡單:

var lis = document.getElementsByTagName('li');
for(var i = 0, len = lis.length; i < len; i++) {
  lis[i].onclick = function() {
    alert(this.innerHTML);
  };
}

但是如上代碼綁定了三個事件,我們知道每個事件綁定都需要占用一定的內存,更糟糕的是,如果在代碼執行過程中,動態地又添加了一個li,這時它沒有綁定click的事件,我們還需要手動添加!這時,我們就可以用到事件委托技術:

var f = document.getElementById('myLink');
f.onclick = function(e) {
  console.log(e.target.innerHTML);
};

好吧,就是這麼簡單!

JavaScript高級程序設計(第3版)高清完整PDF中文+英文+源碼  http://www.linuxidc.com/Linux/2014-09/107426.htm

Copyright © Linux教程網 All Rights Reserved