瀏覽器事件流
事件流描述的是從頁面中接受事件的順序。但有意思的是,IE和Netscape開發團隊居然提出了兩個截然相反的事件流概念。
1、IE的事件流是 事件冒泡流,
2、標準的瀏覽器事件流是 事件捕獲流。
不過addEventLister給出了第三個引數同時支援冒泡與捕獲,下文將介紹
事件冒泡
ie 的事件流叫事件冒泡,也就是說事件的傳播為:從事件開始的具體元素,一級級往上傳播到較為不具體的節點。案例如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>事件冒泡</title> </head> <body> <div> <p>點我</p> </div> </body> </html>
當我們點選P元素時,事件是這樣傳播的:
(1) p
(2) div
(3) body
(4) html
(5) document
現代瀏覽器都支援事件冒泡,IE9、Firefox、Chrome和Safari則將事件一直冒泡到window物件。
事件捕獲
Netscape團隊提出的另一種事件流叫做事件捕獲。它的原理剛好和事件冒泡相反,它的用意在於在事件到達預定目標之前捕獲它,而最具體的節點應該是最後才接收到事件的。
比如還是上面的案例,當點選P元素時,事件的傳播方向就變成了這樣:
(1) document
(2) html
(3) body
(4) div
(5) p
IE9、Firefox、Chrome和Safari目前也支援這種事件流模型,但是有些老版本的瀏覽器不支援,所以很少人使用事件捕獲,而是用事件冒泡的多一點。
DOM事件流
"DOM2級事件"規定的事件流包括三個階段:事件捕獲階段、處於目標階段、事件冒泡階段。
首先發生的事件捕獲,為截獲事件提供機會。然後是實際的目標接受事件。最後一個階段是時間冒泡階段,可以在這個階段對事件做出響應。以前面的例子,則會按下圖順序觸發事件。
圖1-1(圖片來源於課本,所以沒有上面案例的p標籤)
在DOM事件流中,事件的目標在捕獲階段不會接受到事件。這意味著在捕獲階段,事件從document到p後就定停止了。
下一個階段是處於目標階段,於是事件在p上發生,並在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播回document。
多數支援DOM事件流的瀏覽器都實現了一種特定的行為;即使“DOM2級事件”規範明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件物件上的事件。結果,就是有兩個機會在目標物件上操作事件
事件處理程式
事件就是使用者或者瀏覽器自身執行某種動作,比如click、load、mouseover。
而響應某個事件的函式就叫做事件處理程式(事件監聽器),事件處理程式的名字以on開頭,click=>onclick、load=>onload
DOM2提供了兩個方法來讓我們處理和刪除事件處理程式的操作:addEventListener()和removeEventListener
btn.addEventListener(eventType, function () {
}, false);
該方法應用至dom節點
第一個引數為事件名
第二個為事件處理程式
第三個為布林值,true為事件捕獲階段呼叫事件處理程式,false為事件冒泡階段呼叫事件處理程式
使用例子如下:
var btn = document.getElementById(‘btn‘);
btn.addEventListener(‘click‘, function () {
alert(‘事件捕獲‘);
}, true);
btn.addEventListener(‘click‘, function () {
alert(‘事件冒泡‘);
}, false);
依次彈出“事件捕獲”和“事件冒泡”,在這裡的第一反應就是,他們有先後順序嗎?
於是我反過來寫:
var btn = document.getElementById(‘btn‘);
btn.addEventListener(‘click‘, function () {
alert(‘事件冒泡‘);
}, false);
btn.addEventListener(‘click‘, function () {
alert(‘事件捕獲‘);
}, true);
依次彈出的是:“事件冒泡”和“事件捕獲”;到底有沒有先後順序呢?繼續往下走
這次是這樣的寫法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件冒泡</title>
</head>
<body>
<div>
<p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p>
</div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById(‘sonEle‘);
var parEle = document.getElementById(‘parEle‘);
parEle.addEventListener(‘click‘, function () {
alert(‘父級 冒泡‘);
}, false);
parEle.addEventListener(‘click‘, function () {
alert(‘父級 捕獲‘);
}, true);
sonEle.addEventListener(‘click‘, function () {
alert(‘子級冒泡‘);
}, false);
sonEle.addEventListener(‘click‘, function () {
alert(‘子級捕獲‘);
}, true);
</script>
當點選“我是子元素” 時,彈出的順序是:“父級捕獲”--》“子級冒泡”--》“子集捕獲”--》“父集冒泡”;
這裡可以說明,當點選子元素時,父級的執行順序是先捕獲,後冒泡的。
綜合前面的程式碼,我們可以得出一個小小的結論:
當容器元素及巢狀元素,即在捕獲階段
又在冒泡階段
呼叫事件處理程式時:事件按DOM事件流的順序執行事件處理程式,也就是如圖1-1所示
且當事件處於目標階段時,事件呼叫順序決定於繫結事件的書寫順序,按上面的例子為,先呼叫冒泡階段的事件處理程式,再呼叫捕獲階段的事件處理程式。依次alert出“子集冒泡”,“子集捕獲”。
刪除一個事件處理程式,用removeEventListener。例:
var btn=document.getElementById(‘myBtn‘);
var myFn=function(){
alert(this.id);
}
btn.addEventListener(‘click‘,myFn,false);
btn.removeEventListener(‘click‘,myFn,false);
注意點:為了最大限度的相容,大多是情況下都是將事件處理程式新增到事件冒泡階段。不是特別需要,不建議在事件捕獲階段註冊事件處理程式
相容ie瀏覽器寫法:
ie新增和刪除事件處理程式的寫法有點小區別,所以用以下程式碼可以做個相容性處理:
var EventUtil = {
addHandler: function (el, type, handler) {
if (el.addEventListener) {
el.addEventListener(type, handler, false);
} else {
el.attachEvent(‘on‘ + type, handler);
}
},
removeHandler: function (el, type, handler) {
if (el.removeEventListener) {
el.removeEventListerner(type, handler, false);
} else {
el.detachEvent(‘on‘ + type, handler);
}
}
};
用法和前面的類似:
EventUtil.addHandler(‘btn‘,‘click‘,handler);
事件物件
觸發dom上的某個事件時,會產生一個事件物件,裡面包含著所有和事件有關的資訊。
比較常用的有以下幾個:
currentTarget 事件處理程式當前正在處理事件的那個元素(始終等於this)
preventDefault 取消事件預設行為,比如連結的跳轉
stopPropagation 取消事件冒泡
target 事件的目標
相容ie的事件物件:
var EventUtil = {
addHandler: function (el, type, handler) {
if (el.addEventListener) {
el.addEventListener(type, handler, false);
} else if (el.attachEvent) {
el.attachEvent(‘on‘ + type, handler);
} else {
el[‘on‘ + type] = handler;
}
},
removeHandler: function (el, type, handler) {
if (el.removeEventListener) {
el.removeEventListerner(type, handler, false);
} else if (el.detachEvent) {
el.detachEvent(‘on‘ + type, handler);
} else {
el[‘on‘ + type] = null;
}
},
getEvent: function (e) {
return e ? e : window.event;
},
getTarget: function (e) {
return e.target ? 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;
}
}
};
其他
html5事件之 beforeunload
在頁面解除安裝前觸發,就像編輯部落格園文章未儲存是彈出的提示框一樣。
html5事件之 DOMContentLoaded事件
支援頁面下載前新增事件,而不需要等待圖片,css檔案,或者其他檔案載入完畢才執行。可以讓使用者能夠儘早的與使用者進行互動。
EventUtil.addHandler(document,‘DOMContentLoaded‘,function(){
alert(‘我可以先執行,哈哈‘)
})
事件委託:
每個函式都是物件,都會佔用記憶體,記憶體中的物件越多,效能就越差。對事件處理程式過多問題的解決方案就是事件委託。
事件委託利用事件冒泡,只指定一個事件處理程式即可,就可以管理某一個型別的所有事件。例如:
有三個li,都需要一個click事件,此時不需要給每個li都繫結click事件,主要給他的父級 ul增加一個繫結事件即可。這樣點選li,利用冒泡,直接觸發ul的click,只要判斷是哪個li的id
點選即可。而不需要三個li都繫結click事件。
<ul id="myLinks">
<li id="myLi1">text1</li>
<li id="myLi2">text2</li>
<li id="myLi3">text3</li>
</ul>
事件委託原理:事件冒泡機制。 優點:1.可以大量節省記憶體佔用,減少事件註冊。比如ul上代理所有li的click事件就很不錯。 2.可以實現當新增子物件時,無需再對其進行事件繫結,對於動態內容部分尤為合適 缺點:事件代理的常用應用應該僅限於上述需求,如果把所有事件都用事件代理,可能會出現事件誤判。即本不該被觸發的事件被繫結上了事件。
二、事件委託如何工作?
我們現在的疑問是:ul元素如何知道li元素點選了呢?
很簡單,由於所有li元素都是ul元素的子節點,故他們的事件會冒泡,無論點選哪個li元素,實際上都相當於點選了ul元素。
現在產生了另一個問題:ul元素如何知道是在哪個li元素上點選的呢?
我們很容易想到,在ul的事件處理程式中檢測事件物件的target屬性,就可以得到真正點選的目標元素。
三、事件委託的優點
首先,我們看到新增的事件處理程式減少,可以只有一個事件處理程式。由於每個函式都是物件,物件會佔用記憶體,記憶體的佔用關係到效能。因此第一個優點是:
減少了記憶體佔用,效能更好;
在訪問DOM方面,也使得DOM訪問次數減少。試想一下,如果要為許多的DOM元素繫結事件,自然需要多次訪問DOM元素,設定事件處理程式所需時間更長,整個頁面就緒需要的時間越多。因此第二個優點是:
設定事件處理程式所需時間更少,加快了整個頁面的互動就緒時間。
假使我們將事件處理程式繫結到document物件上,只要可單擊的元素呈現在頁面上,就可以立即具備適當的功能。即還會有一個額外的優點:
document很快就可以訪問,而且可以在頁面生命週期的任何時點新增事件處理程式,而不用等待其他事件完成如DOMContentLoaded、load事件。