一、事件流
定義:事件流描述的是從頁面中接收事件的順序。
但有意思的是,IE 和Netscape 開發(fā)團隊居然提出了差不多是完全相反的事件流的概念。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕獲流。
1、事件冒泡(event-bubbling)
IE 的事件流叫做事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
如上代碼,如果點擊了id位myDIv的元素后,之后會按照如圖傳播
2、事件捕獲(event-capturing)
事件捕獲的思想是不太具體的節(jié)點應(yīng)該更早接收到事件,而最具體的節(jié)點應(yīng)該最后接收到事件。事件捕獲的用意在于在事件到達預(yù)定目標之前捕獲它。
由于老版本的瀏覽器不支持,因此很少有人使用事件捕獲。我們也建議讀者放心地使用事件冒泡,在有特殊需要時再使用事件捕獲。
3、DOM事件流
“DOM2級事件”規(guī)定的事件流包括三個階段:事件捕獲階段、處于目標階段和事件冒泡階段。
在DOM 事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件從document 到<html>再到<body>后就停止了。下一個階段是“處于目標”階段,于是事件在<div>上發(fā)生,并在事件處理(后面將會討論這個概念)中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生,事件又傳播回文檔。
多數(shù)支持DOM 事件流的瀏覽器都實現(xiàn)了一種特定的行為;即使“DOM2 級事件”規(guī)范明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox 和Opera 9.5 及更高版本都會在捕獲階段觸發(fā)事件對象上的事件。結(jié)果,就是有兩個機會在目標對象上面操作事件。
二、事件處理程序
我們先來了解幾個概念
事件:事件就是用戶或瀏覽器自身執(zhí)行的某種動作。諸如click、load 和mouseover,都是事件的名字
事件處理程序:響應(yīng)某個事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,因此click 事件的事件處理程序就是onclick,load 事件的事件處理程序就是onload。為事件指定處理程序的方式有好幾種
1、HTML事件處理程序
某個元素支持的每種事件,都可以使用一個與相應(yīng)事件處理程序同名的HTML 特性來指定。這個特性的值應(yīng)該是能夠執(zhí)行的JavaScript 代碼
<input type="button" value="Click me" onclick="alert('hello')">
特點:
- 會創(chuàng)建一個封裝著元素屬性值的函數(shù)。這個函數(shù)中有一個局部變量event,也就是事件對象。通過event 變量,可以直接訪問事件對象,你不用自己定義它,也不用從函數(shù)的參數(shù)列表中讀取。
<button onclick="alert(event.type)">輸出click</button>
- 在這個函數(shù)內(nèi)部,this 值等于事件的目標元素
2、DOM0 級事件處理程序
簡單的說:就是將一個函數(shù)賦值給一個事件處理程序?qū)傩浴@纾?/p>
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
}
使用DOM0 級方法指定的事件處理程序被認為是元素的方法。因此,這時候的事件處理程序是在元素的作用域中運行;換句話說,程序中的this 引用當前元素。
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(this.id);
}
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
也可以刪除通過DOM0 級方法指定的事件處理程序,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為null 即可
btn.onclick = null;
DOM0 級對每個事件只支持一個事件處理程序。
2、DOM2級事件處理程序
“DOM2級事件”定義了兩個方法,用于處理指定和刪除事件處理程序的操作:addEventListener()和removeEventListener()。所有DOM節(jié)點中都包含這兩個方法,并且它們都接受3 個參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個布爾值。最后這個布爾值參數(shù)如果是true,表示在捕獲階段調(diào)用事件處理程序;如果是false,表示在冒泡階段調(diào)用事件處理程序。
btn.addEventListener("click",function(){
alert(this.id);
},false);
通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數(shù)與添加處理程序時使用的參數(shù)相同。這也意味著通過addEventListener()添加的匿名函數(shù)將無法移除
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);
IE9、Firefox、Safari、Chrome 和Opera 支持DOM2 級事件處理程序。
3、IE事件處理程序
attchEvent(eventType, function) detachEvent(eventType, function)
在使用DOM0 級方法的情況下,事件處理程序會在其所屬元素的作用域內(nèi)運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this 等于window。
btn.attachEvent("oncick", function(){
alert(this === window);
});
三、事件對象
在觸發(fā)DOM上的某個事件時,會產(chǎn)生一個事件對象event,這個對象中包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。
1、DOM中的事件對象
兼容DOM 的瀏覽器會將一個event 對象傳入到事件處理程序中。無論指定事件處理程序時使用什么方法(DOM0 級或DOM2 級),都會傳入event 對象。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(event.type); // click
}
btn.addEventListener("click",function(event){
alert(event.type); // click
},false);
var btn = document.getElementById("myBtn");
document.body.onclick = function() {
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
}
preventDefault()
阻止特定事件的默認行為
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
}
stopPropagation()
立即停止事件在DOM層次的傳播,即取消進一步的事件捕獲或冒泡
btn.onclick = function(event) {
alert("clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("body clicked");
}
eventPhase屬性
確定事件當前正位于事件流的哪個階段
捕獲階段:1
目標對象上:2
冒泡階段:3
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener("click",
function(event){
alert(event.eventPhase); //1
},true);
document.body.onclick = function(event){
alert(event.eventPhase); //3
}
**注意:只有在事件處理程序執(zhí)行期間,event 對象才會存在;一旦事件處理程序執(zhí)行完成,event 對象就會被銷毀。 **
2、IE中的事件對象
在使用DOM0 級方法添加事件處理程序時,event 對象作為window 對象的一個屬性存在
attachEvent()添加的,那么就會有一個event 對象作為參數(shù)被傳入事件處理程序函數(shù)中
3、兼容性的事件對象
var EventUtil = {
addHandler:function(element, type, handler){
if(element.addEventListener){
element.addEventListener(type, handler, false);
}else if(element.attchEvent) {
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;
}
},
getEvent: function(event){
return event ? event : window.event;
},
getTarget:function(event){
return event.target || event.srcElement;
},
preventDefault:function(event) {
if(event.preventDefault) {
event.preventDefault();
}else{
event.returnValue = false;
}
},
stopPropagation: function(event) {
if(event.stopPropagation) {
event.stopPropagation();
}else{
event.cancelBubble = true;
}
}
};
四、事件類型
1、UI事件
load
當頁面完全加載后(包括所有圖像、JavaScript 文件、CSS 文件等外部資源),就會觸發(fā)window 上面的load 事件。
unload
與load 事件對應(yīng)的是unload 事件,這個事件在文檔被完全卸載后觸發(fā)。只要用戶從一個頁面切換到另一個頁面,就會發(fā)生unload 事件。而利用這個事件最多的情況是清除引用,以避免內(nèi)存泄漏。
resize
當瀏覽器窗口被調(diào)整到一個新的高度或?qū)挾葧r,就會觸發(fā)resize 事件。
EventUtil.addHandler(window, "resize", function(event){
alert("Resized");
});
scroll
2、焦點事件
focus
在元素獲得焦點時觸發(fā)
blur
在元素失去焦點時觸發(fā)
3、鼠標和滾輪事件
click
dblclick
mousedown
mouseup
mouseenter
mouseleave
mouseover
mouseout
- 客戶區(qū)坐標位置
var div = document.getElementById('myDiv');
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Client coordinates:" + event.clientX + "," + event.clientY);
});
- 頁面坐標位置
頁面坐標通過事件對象的pageX 和pageY 屬性,能告訴你事件是在頁面中的什么位置發(fā)生的 - 屏幕坐標位置
過screenX 和screenY 屬性就可以確定鼠標事件發(fā)生時鼠標指針相對于整個屏幕的坐標信息
…………更多內(nèi)容參考紅皮書
五、內(nèi)存和性能
1、事件委托
假設(shè)如果我們?yōu)轫撁嫔系拿恳粋€需要事件的元素都添加事件處理程序,那么會嚴重影響頁面的整體性能。原因有一下幾點:
- 每個函數(shù)都是對象,都會占用內(nèi)存
- 必須事先指定所有事件處理程序而導(dǎo)致的DOM訪問次數(shù),會延遲整個頁面的交互就緒時間
所以我們使用事件委托的方式,其原理就是使用事件的冒泡機制。具體看例子
平常的操作:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script src="eventUtil.js"></script>
<script>
var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
location.;
});
EventUtil.addHandler(item2, "click", function(event){
document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
alert("hi");
})
</script>
</body>
</html>
利用事件委托:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script src="eventUtil.js"></script>
<script>
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
document.title = "i changed the title";
break;
case "goSomewhere":
location.;
break;
case "sayHi":
alert("hi");
break;
}
})
</script>
</body>
</html>
這樣我們只需要把事件處理程序放到ul這一個DOM節(jié)點上,然后根據(jù)target的值來判斷用哪個事件,就可以避免頁面中寫大量的事件處理程序(event handler)了,也減少了DOM的操作。
2、移除event handler
事件委托是限制event handler的數(shù)量。另外可以通過移除不用的空事件處理程序(dangling event handler)來提高性能。
造成dangling event handler的原因可能有兩種:
- 從文檔中移除帶有事件處理程序的元素時,這可能是通過純粹的DOM操作,例如使用removeChild()和replaceChild()方法
- 使用innerHTML 替換頁面中某一部分的時候。如果帶有事件處理程序的元素被innerHTML 刪除了,那么原來添加到元素中的事件處理程序極有可能無法被當作垃圾回收
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script>
var btn = document.getElementById("myBtn");
btn.onclick = function(){
document.getElementById("myDiv").innerHTML = "Processing……"
}
</script>
</body>
</html>
當按鈕被從頁面中移除時,它還帶著一個事件處理程,仍然占用著內(nèi)存。
解決辦法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script>
var btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null; //移除事件處理程序
document.getElementById("myDiv").innerHTML = "Processing……"
}
</script>
</body>
</html>
建議瀏覽器卸載頁面之前移除頁面中的所有事件處理程序。