JavaScript學習之事件原理和實踐

1 基本概念  

1.1 事件

  JavaScript與HTML之間的交互是經過事件實現的。javascript

  事件是文檔或瀏覽器窗口中發生的一些特定的交互瞬間,在事件上能夠註冊處理程序,以便當事件發生時,處理程序中的代碼獲得執行(這種模型對應設計模式中的觀察者模式)。html

1.2 事件流

  事件流描述的是從頁面接受事件的順序。java

2 事件流

2.1 事件冒泡

  IE事件流叫作事件冒泡,即事件開始由最具體的元素接受,而後逐漸向上傳播到較爲不具體的節點。設計模式

  事件冒泡可以被全部瀏覽器支持。注意IE5.5及更早版本的事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9,Firefox,Chrome和Safari則將事件一直冒泡到window對象。瀏覽器

2.2 事件捕獲

  事件捕獲是由不太具體的節點最先接受到事件,而最具體的元素最後接受到事件。事件捕獲的用意在於在事件到達預約的目標以前捕獲它。函數

  事件捕獲不支持老版本的瀏覽器,但IE9,Firefox,Chrome,Opera和Safari目前都支持這種事件流模型性能

2.3 DOM事件流

  "DOM2事件流"包括三個階段:事件捕獲階段,處於目標階段和事件冒泡階段;在DOM事件流中,實際的目標在捕獲階段不會接受到事件(但IE九、Safari、Chrome、Firefox和Opera 9.5及更高版本都會在捕獲階段觸發事件對象上的事件,即有兩個機會在目標對象上面操做事件)。this

  IE9,Firefox,Chrome,Opera和Safari都支持DOM事件流;IE8及更早版本不支持DOM事件流。設計

3 事件處理程序

  事件是用戶或瀏覽器自身執行的某種動做,常見的事件有click,focus,load;而響應事件的函數叫作事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,例如click事件對應的事件處理程序就是onclick,focus事件對應的事件處理程序就是onfocus,load事件對應的事件處理程序就是onload。htm

3.1 HTML事件處理程序

  HTML元素支持的每種事件,均可以用一個與相應事件處理程序同名的HTML特性來指定。這個特性的值應該是可以執行的JavaScript代碼。例如:

  <input type="button" value="click me" onclick="showMessage()"/>

  HTML中指定事件處理程序有兩個缺點:

  首先存在時差問題。若是在HTML元素一出如今頁面就觸發相應的事件,但當時的事件處理程序有可能尚不具有執行條件。具體說來,上例中若是事件處理程序showMessage是在按鈕下方,頁面底部定義的,當用戶在頁面解析該函數以前就單擊了按鈕,就會引起錯誤;

  另外一個缺點是,這樣擴展事件處理程序的做用鏈在不一樣瀏覽器中會致使不一樣結果。

  經過HTML指定事件處理程序的另外一個缺點是HTML和JavaScript代碼緊密耦合。若是要更換事件處理程序,須要同時改動兩個地方:HTML代碼和JavaScript代碼。

3.2 DOM0級事件處理程序

  指定事件處理程序的傳統方式就是將一個函數賦值給一個事件處理程序屬性。這種方式優勢:1.簡單,2.具備跨瀏覽器的優點。爲元素指定事件處理程序分兩步:

  (1)取得要操做對象的引用;

  (2)將該元素的事件處理程序屬性如onclick設置爲一個函數。示例以下:  

<input type="button" value="click me" id="myBtn"/>
<script>
	var btn = document.getElementById('myBtn');
	btn.onclick = function(){ alert("I am clicked!"); }
</script>

  使用DOM0級方法指定的事件處理程序被認定爲是元素的方法,即事件處理程序是在元素的做用域中運行,此時this引用當前元素

  刪除DOM0級事件處理程序:btn.onclick = null;

3.3 DOM2級事件處理程序

  DOM2級事件定義了兩個方法,用於處理指定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。這兩個方法都接受三個參數:要處理的事件名稱,事件處理程序函數,布爾值。其中布爾值爲true表示在捕獲階段調用事件處理程序,不然表示在冒泡階段調用事件處理程序。

  與DOM0級事件處理程序同樣,DOM2級事件處理程序也是在依附的元素的做用域中運行。使用DOM2級方法添加事件處理程序的好處是能夠添加多個事件處理程序。示例以下:

<input type="button" value="click me" id="myBtn"/>
<script>
	var btn = document.getElementById('myBtn');

	btn.addEventListener("click", function(){
		alert("first");
	}, false);
	btn.addEventListener("click", function(){
		alert("second");
	}, false);
</script>

  運行後,前後彈內容爲"first"和"second"的對話框,說明能夠經過addEventListener爲元素添加多個事件處理程序。

  經過addEventListener()添加的事件處理程序只能經過removeEventListener()來移除;移除時傳入的參數與添加時傳入的參數相同。注意經過addEventListener()添加的匿名函數將沒法移除

<input type="button" value="click me" id="myBtn"/>
<script>
	var btn = document.getElementById('myBtn');

	btn.addEventListener("click", function(){
		alert("first");
	}, false);

	btn.removeEventListener("click", function(){	//失效:刪除匿名函數並非同一個函數
		alert("first");
	}, false);

	var secondFunc = function(){
		alert("second");
	};
	btn.addEventListener("click", secondFunc, false);
	btn.removeEventListener("click", secondFunc, false);//成功:刪除同一個處理函數
</script>

  運行後只有一個彈窗,內容爲"first",說明在使用removeEventListener()刪除addEventListener()添加的事件處理函數時,必須保證第二個參數非匿名函數。

  DOM2級事件處理程序的添加大多數都是在事件的冒泡階段(第三個參數爲false),這樣能夠最大限度地兼容各類瀏覽器。

  IE9,Firefox,Chrome,Opera和Safari支持DOM2級事件處理程序

3.4 IE事件處理程序

  IE實現了與DOM中相似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱和事件處理程序函數。因爲IE8及更早的版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序都會被添加到冒泡階段。

  在IE中使用attachEvent()與使用DOM0級方法的主要區別是事件處理程序的做用域,使用attachEvent()方法的狀況下,事件處理程序會在全局做用域運行,所以this === window

  attachEvent()添加多個事件處理程序,執行順序與添加順序相反。

  使用attachEvent()添加的事件能夠經過detachEvent()來移除,條件是必須提供相同的參數。與DOM方法同樣,添加的匿名函數不能被移除。

  支持IE事件處理程序的瀏覽器有IE和Opera。

3.5 跨瀏覽器的事件處理程序

  要保證事件處理程序在大多數瀏覽器中一致地運行,只需關注冒泡階段。  

var EventUtil = {
	addHandler: function(element, type, handler){
		if(element.addEventListener){
			element.addEventListener(type, handler, false);
		}
		else if(element.attachEvent){
			element.attachEvent("on" + type, handler);
		}
		else{
			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;
		}
	}
};

4 事件對象

4.1 DOM事件對象

  兼容DOM(DOM0級和DOM2級)的瀏覽器會將一個event對象傳入到事件處理程序中。

  要阻止特定的默認行爲用preventDefault()方法,要當即中止事件在DOM層次中的傳播用stopPropagation()方法

  在事件處理程序中,對象this始終等於currentTarget的值,而target則只包含事件的實際目標

4.2 IE事件對象

  與訪問DOM中的event對象不一樣,IE中的event對象有幾種不一樣的方式,取決於指定事件處理程序的方法。

  使用DOM0級方法添加事件處理程序時,event做爲window對象的一個屬性存在;若是使用attachEvent()添加事件處理程序,那麼就會有一個event對象做爲參數被傳入事件處理函數中(也能夠經過window對象來訪問event對象)。

  IE的event對象包含於建立它的事件相關的屬性和方法。

  其中cancelBubble設置爲true與DOM中stopPropagation()方法的做用相同;

  returnValue設置爲false與DOM中preventDefault()方法的做用相同。

  srcElement表示事件的目標,與DOM中target屬性相同。

  type被觸發的事件類型與DOM中type屬性相同。

4.3 跨瀏覽器的事件對象

  在前面的EventUtil中添加方法來處理DOM和IE中event對象的差別(如下只展現添加的代碼):  

getEvent: function(event){
  return event ? event : window.event;
},
getTarget: function(event){
  return event.target || event.srcElement;
},
preventDefalut: function(event){
  if(event.preventDefalut){
    event.preventDefalut();  
	}
  else{
    event.returnValue = false;
  }
},
stopPropagation: function(event){
  if(event.stopPropagation){
    event.stopPropagation();
  }
  else{
    event.cancelBubble = true;
  }
}

5 事件委託

5.1 內存和性能

  在JavaScript中,添加到頁面上的事件處理程序的數量直接關係到頁面的總體運行性能

  首先,每一個函數都是對象,都會佔用內存;內存中的對象越多,性能就越差。

  其次,必須事先指定全部事件處理程序,會致使DOM訪問次數增長,會延遲整個頁面的交互就緒時間。

5.2 事件委託

  事件委託就是事件目標自己不處理事件,而是把處理任務委託給其父元素或祖先元素。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。

  經過將事件處理程序委託給父元素或祖先元素,事件委託解決了"事件處理程序過多"的問題。

  假定如今有個ul無序列表(id爲items),其中li(id爲item+序號)表明每一個項,當點擊項時需彈窗顯示項的基本信息。

<ul id="items">
	<li id="item1">無序列表1</li>
	<li id="item2">無序列表2</li>
	<li id="item3">無序列表3</li>
</ul>

  最簡單的實現方式就是直接在li元素上進行事件綁定(addHandler是跨瀏覽器的添加事件處理程序函數,具體實現見JavaScript事件章節):  

var item1 = document.getElementById('item1');
var item2 = document.getElementById('item2');
var item3 = document.getElementById('item3');

EventUtil.addHandler(item1, "click", function(){
	alert("I am item1!");
});

EventUtil.addHandler(item2, "click", function(){
	alert("I am item2!");
});

EventUtil.addHandler(item3, "click", function(){
	alert("I am item3!");
});

  使用事件委託,只需在DOM樹種儘可能最高的層次添加一個事件處理程序。

var items = document.getElementById('items');
EventUtil.addHandler(items, "click", function(event){
	var event = EventUtil.getEvent(event);
	var target = EventUtil.getTarget(event);

	alert("I am " + target.id);
});

  這裏,經過事件委託只爲ul元素添加了一個onclick的事件處理程序。因爲全部li都是這個ul的子節點,並且它們的事件會冒泡,因此單擊事件最終會被這個函數處理。

  對比普通的事件綁定代碼和使用事件委託後的代碼,會發現使用事件委託後消耗更小:只獲取了一個DOM節點,只添加了一個事件處理程序,佔用的內存更小。

  全部用到按鈕的事件(多數鼠標事件和鍵盤事件)都適合採用委託的技術。

5.3 移除事件處理程序

  減小瀏覽器代碼和支持頁面交互的JavaScript代碼之間的鏈接,一般有兩種方法:

  1.使用事件委託技術,限制創建的鏈接數量;

  2.在不須要的時候,移除事件處理程序

  若是沒有很好地移除事件處理程序,會致使"空事件處理程序",進而也會影響程序內存和性能,形成空事件處理程序的緣由:

  1.從文檔中移除了帶有事件處理程序的元素(removeChild(),replaceChild());

  2.帶有事件處理程序的元素被innerHTML刪除了。解決辦法在innerHTML操做前經過給事件賦值null移除事件處理程序

  3.頁面卸載的時候(IE8-),若是在頁面被卸載前沒有清理乾淨事件處理程序,那麼它們就會滯留在內存。解決辦法經過onunload事件處理程序移除全部事件處理程序。

相關文章
相關標籤/搜索