浏览器事件流
事件流描述的是從頁(yè)面中接受事件的順序。但有意思的是,IE和Netscape開發(fā)團(tuán)隊(duì)居然提出了兩個(gè)截然相反的事件流概念。
1、IE的事件流是 事件冒泡流,
?2、標(biāo)準(zhǔn)的瀏覽器事件流是 事件捕獲流。
? ? ?不過addEventLister給出了第三個(gè)參數(shù)同時(shí)支持冒泡與捕獲,下文將介紹
?
事件冒泡
ie 的事件流叫事件冒泡,也就是說事件的傳播為:從事件開始的具體元素,一級(jí)級(jí)往上傳播到較為不具體的節(jié)點(diǎn)。案例如下:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>事件冒泡</title> </head> <body><div><p>點(diǎn)我</p></div> </body> </html>當(dāng)我們點(diǎn)擊P元素時(shí),事件是這樣傳播的:
(1) p
(2) div
(3) body
(4) html
(5) document
現(xiàn)代瀏覽器都支持事件冒泡,IE9、Firefox、Chrome和Safari則將事件一直冒泡到window對(duì)象。
?
事件捕獲
? ?Netscape團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲。它的原理剛好和事件冒泡相反,它的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它,而最具體的節(jié)點(diǎn)應(yīng)該是最后才接收到事件的。
比如還是上面的案例,當(dāng)點(diǎn)擊P元素時(shí),事件的傳播方向就變成了這樣:
(1) document
(2) html
(3) body
(4) div
(5) p
?
IE9、Firefox、Chrome和Safari目前也支持這種事件流模型,但是有些老版本的瀏覽器不支持,所以很少人使用事件捕獲,而是用事件冒泡的多一點(diǎn)。
?
DOM事件流
? "DOM2級(jí)事件"規(guī)定的事件流包括三個(gè)階段:事件捕獲階段、處于目標(biāo)階段、事件冒泡階段。
首先發(fā)生的事件捕獲,為截獲事件提供機(jī)會(huì)。然后是實(shí)際的目標(biāo)接受事件。最后一個(gè)階段是時(shí)間冒泡階段,可以在這個(gè)階段對(duì)事件做出響應(yīng)。以前面的例子,則會(huì)按下圖順序觸發(fā)事件。
圖1-1(圖片來源于課本,所以沒有上面案例的p標(biāo)簽)
在DOM事件流中,事件的目標(biāo)在捕獲階段不會(huì)接受到事件。這意味著在捕獲階段,事件從document到p后就定停止了。
下一個(gè)階段是處于目標(biāo)階段,于是事件在p上發(fā)生,并在事件處理中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生,事件又傳播回document。??
? ? ?多數(shù)支持DOM事件流的瀏覽器都實(shí)現(xiàn)了一種特定的行為;即使“DOM2級(jí)事件”規(guī)范明確要求捕獲階段不會(huì)涉及事件目標(biāo),但I(xiàn)E9、Safari、Chrome、Firefox和Opera9.5及更高版本都會(huì)在捕獲階段觸發(fā)事件對(duì)象上的事件。結(jié)果,就是有兩個(gè)機(jī)會(huì)在目標(biāo)對(duì)象上操作事件
?
事件處理程序
事件就是用戶或者瀏覽器自身執(zhí)行某種動(dòng)作,比如click、load、mouseover。 而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(事件監(jiān)聽器),事件處理程序的名字以on開頭,click=>onclick、load=>onloadDOM2提供了兩個(gè)方法來讓我們處理和刪除事件處理程序的操作:addEventListener()和removeEventListener btn.addEventListener(eventType, function () { }, false);該方法應(yīng)用至dom節(jié)點(diǎn) 第一個(gè)參數(shù)為事件名 第二個(gè)為事件處理程序 第三個(gè)為布爾值,true為事件捕獲階段調(diào)用事件處理程序,false為事件冒泡階段調(diào)用事件處理程序?
使用例子如下:
var btn = document.getElementById(‘btn‘);btn.addEventListener(‘click‘, function () {alert(‘事件捕獲‘);}, true);btn.addEventListener(‘click‘, function () {alert(‘事件冒泡‘);}, false);依次彈出“事件捕獲”和“事件冒泡”,在這里的第一反應(yīng)就是,他們有先后順序嗎?
于是我反過來寫:
var btn = document.getElementById(‘btn‘);btn.addEventListener(‘click‘, function () {alert(‘事件冒泡‘);}, false);btn.addEventListener(‘click‘, function () {alert(‘事件捕獲‘);}, true);依次彈出的是:“事件冒泡”和“事件捕獲”;到底有沒有先后順序呢?繼續(xù)往下走
這次是這樣的寫法:
<!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(‘父級(jí) 冒泡‘); }, false); parEle.addEventListener(‘click‘, function () {alert(‘父級(jí) 捕獲‘); }, true);sonEle.addEventListener(‘click‘, function () {alert(‘子級(jí)冒泡‘); }, false); sonEle.addEventListener(‘click‘, function () {alert(‘子級(jí)捕獲‘); }, true);</script>當(dāng)點(diǎn)擊“我是子元素” 時(shí),彈出的順序是:“父級(jí)捕獲”--》“子級(jí)冒泡”--》“子集捕獲”--》“父集冒泡”;
這里可以說明,當(dāng)點(diǎn)擊子元素時(shí),父級(jí)的執(zhí)行順序是先捕獲,后冒泡的。
綜合前面的代碼,我們可以得出一個(gè)小小的結(jié)論:
當(dāng)容器元素及嵌套元素,即在捕獲階段又在冒泡階段調(diào)用事件處理程序時(shí):事件按DOM事件流的順序執(zhí)行事件處理程序,也就是如圖1-1所示
且當(dāng)事件處于目標(biāo)階段時(shí),事件調(diào)用順序決定于綁定事件的書寫順序,按上面的例子為,先調(diào)用冒泡階段的事件處理程序,再調(diào)用捕獲階段的事件處理程序。依次alert出“子集冒泡”,“子集捕獲”。
?
刪除一個(gè)事件處理程序,用removeEventListener。例:
var btn=document.getElementById(‘myBtn‘); var myFn=function(){alert(this.id); } btn.addEventListener(‘click‘,myFn,false); btn.removeEventListener(‘click‘,myFn,false);?
注意點(diǎn):為了最大限度的兼容,大多是情況下都是將事件處理程序添加到事件冒泡階段。不是特別需要,不建議在事件捕獲階段注冊(cè)事件處理程序
?
兼容ie瀏覽器寫法:
?
ie添加和刪除事件處理程序的寫法有點(diǎn)小區(qū)別,所以用以下代碼可以做個(gè)兼容性處理:
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);?
?
事件對(duì)象
觸發(fā)dom上的某個(gè)事件時(shí),會(huì)產(chǎn)生一個(gè)事件對(duì)象,里面包含著所有和事件有關(guān)的信息。
比較常用的有以下幾個(gè):
currentTarget 事件處理程序當(dāng)前正在處理事件的那個(gè)元素(始終等于this)preventDefault 取消事件默認(rèn)行為,比如鏈接的跳轉(zhuǎn)stopPropagation 取消事件冒泡target 事件的目標(biāo)
兼容ie的事件對(duì)象:
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
在頁(yè)面卸載前觸發(fā),就像編輯博客園文章未保存是彈出的提示框一樣。
?
html5事件之 DOMContentLoaded事件
支持頁(yè)面下載前添加事件,而不需要等待圖片,css文件,或者其他文件加載完畢才執(zhí)行。可以讓用戶能夠盡早的與用戶進(jìn)行交互。
EventUtil.addHandler(document,‘DOMContentLoaded‘,function(){alert(‘我可以先執(zhí)行,哈哈‘) })?
事件委托:
? ?每個(gè)函數(shù)都是對(duì)象,都會(huì)占用內(nèi)存,內(nèi)存中的對(duì)象越多,性能就越差。對(duì)事件處理程序過多問題的解決方案就是事件委托。
? ?事件委托利用事件冒泡,只指定一個(gè)事件處理程序即可,就可以管理某一個(gè)類型的所有事件。例如:
有三個(gè)li,都需要一個(gè)click事件,此時(shí)不需要給每個(gè)li都綁定click事件,主要給他的父級(jí) ul增加一個(gè)綁定事件即可。這樣點(diǎn)擊li,利用冒泡,直接觸發(fā)ul的click,只要判斷是哪個(gè)li的id
點(diǎn)擊即可。而不需要三個(gè)li都綁定click事件。
<ul id="myLinks"><li id="myLi1">text1</li><li id="myLi2">text2</li><li id="myLi3">text3</li> </ul>?
事件委托原理:事件冒泡機(jī)制。 優(yōu)點(diǎn):1.可以大量節(jié)省內(nèi)存占用,減少事件注冊(cè)。比如ul上代理所有l(wèi)i的click事件就很不錯(cuò)。 2.可以實(shí)現(xiàn)當(dāng)新增子對(duì)象時(shí),無需再對(duì)其進(jìn)行事件綁定,對(duì)于動(dòng)態(tài)內(nèi)容部分尤為合適 缺點(diǎn):事件代理的常用應(yīng)用應(yīng)該僅限于上述需求,如果把所有事件都用事件代理,可能會(huì)出現(xiàn)事件誤判。即本不該被觸發(fā)的事件被綁定上了事件。二、事件委托如何工作?
我們現(xiàn)在的疑問是:ul元素如何知道li元素點(diǎn)擊了呢?
很簡(jiǎn)單,由于所有l(wèi)i元素都是ul元素的子節(jié)點(diǎn),故他們的事件會(huì)冒泡,無論點(diǎn)擊哪個(gè)li元素,實(shí)際上都相當(dāng)于點(diǎn)擊了ul元素。
現(xiàn)在產(chǎn)生了另一個(gè)問題:ul元素如何知道是在哪個(gè)li元素上點(diǎn)擊的呢?
我們很容易想到,在ul的事件處理程序中檢測(cè)事件對(duì)象的target屬性,就可以得到真正點(diǎn)擊的目標(biāo)元素。
三、事件委托的優(yōu)點(diǎn)
首先,我們看到添加的事件處理程序減少,可以只有一個(gè)事件處理程序。由于每個(gè)函數(shù)都是對(duì)象,對(duì)象會(huì)占用內(nèi)存,內(nèi)存的占用關(guān)系到性能。因此第一個(gè)優(yōu)點(diǎn)是:
減少了內(nèi)存占用,性能更好;
在訪問DOM方面,也使得DOM訪問次數(shù)減少。試想一下,如果要為許多的DOM元素綁定事件,自然需要多次訪問DOM元素,設(shè)置事件處理程序所需時(shí)間更長(zhǎng),整個(gè)頁(yè)面就緒需要的時(shí)間越多。因此第二個(gè)優(yōu)點(diǎn)是:
設(shè)置事件處理程序所需時(shí)間更少,加快了整個(gè)頁(yè)面的交互就緒時(shí)間。
假使我們將事件處理程序綁定到document對(duì)象上,只要可單擊的元素呈現(xiàn)在頁(yè)面上,就可以立即具備適當(dāng)?shù)墓δ堋<催€會(huì)有一個(gè)額外的優(yōu)點(diǎn):
document很快就可以訪問,而且可以在頁(yè)面生命周期的任何時(shí)點(diǎn)添加事件處理程序,而不用等待其他事件完成如DOMContentLoaded、load事件。
總結(jié)
- 上一篇: 短视频自媒体5大必备工具,新手抓紧收藏,
- 下一篇: 浏览器事件是否冒泡