浏览器事件流
事件流描述的是從頁面中接受事件的順序。但有意思的是,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=>onloadDOM2提供了兩個方法來讓我們處理和刪除事件處理程序的操作: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事件。
總結
- 上一篇: 短视频自媒体5大必备工具,新手抓紧收藏,
- 下一篇: 浏览器事件是否冒泡