一.事件流事件冒泡和事件捕獲分別由微軟和網(wǎng)景公司提出,這兩個(gè)概念是為了解決頁面中事件流(事件發(fā)生順序)的問題。 <div id="outer"> <p id="inner">Click me!</p> </div> 上面的代碼當(dāng)中一個(gè)div元素當(dāng)中有一個(gè)p子元素,如果兩個(gè)元素都有一個(gè)click的處理函數(shù),那么我們怎么才能知道哪一個(gè)函數(shù)會首先被觸發(fā)呢? 為了解決這個(gè)問題微軟和網(wǎng)景提出了兩種幾乎完全相反的概念。 1.事件冒泡微軟提出了名為事件冒泡的事件流。事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。也就是說,事件會從最內(nèi)層的元素開始發(fā)生,一直向上傳播,直到document對象。 因此上面的例子在事件冒泡的概念下發(fā)生click事件的順序應(yīng)該是p -> div -> body -> html -> document 2.事件捕獲網(wǎng)景提出另一種事件流名為事件捕獲與事件冒泡相反,事件會從最外層開始發(fā)生,直到最具體的元素。 上面的例子在事件捕獲的概念下發(fā)生click事件的順序應(yīng)該是document -> html -> body -> div -> p 3.W3C事件階段(event phase):當(dāng)一個(gè)DOM事件被觸發(fā)的時(shí)候,他并不是只在它的起源對象上觸發(fā)一次,而是會經(jīng)歷三個(gè)不同的階段。簡而言之:事件一開始從文檔的根節(jié)點(diǎn)流向目標(biāo)對象(捕獲階段),然后在目標(biāo)對向上被觸發(fā)(目標(biāo)階段),之后再回溯到文檔的根節(jié)點(diǎn)(冒泡階段)如圖所示(圖片來自W3C): 事件捕獲階段(Capture Phase) 事件從文檔的根節(jié)點(diǎn)出發(fā),隨著DOM樹的結(jié)構(gòu)向事件的目標(biāo)節(jié)點(diǎn)流去。途中經(jīng)過各個(gè)層次的DOM節(jié)點(diǎn),并在各節(jié)點(diǎn)上觸發(fā)捕獲事件,直到到達(dá)時(shí)間的目標(biāo)節(jié)點(diǎn)。捕獲階段的主要任務(wù)是簡歷傳播路徑,在冒泡階段,時(shí)間會通過這個(gè)路徑回溯到文檔根節(jié)點(diǎn)。 例如,通過下面的這個(gè)函數(shù)來給節(jié)點(diǎn)設(shè)置監(jiān)聽,可以通過將;設(shè)置成true來為事件的捕獲階段添加監(jiān)聽回調(diào)函數(shù)。 element.removeEventListener(<event-name>, <callback>, <use-capture>); 而,在實(shí)際應(yīng)用中,我們并沒有太多使用捕獲階段監(jiān)聽的用例,但是通過在捕獲階段對事件的處理,我們可以阻止類似click事件在某個(gè)特定元素上被觸發(fā)。如下: var form=document.querySeletor('form'); form.addEventListener('click',function(e){ e.stopPropagation(); },true); 如果你對這種用法不是很了解的話,建議設(shè)置為false或者undefined,從而在冒泡階段對事件進(jìn)行監(jiān)聽,這也是常用的方法。 目標(biāo)階段(Target Phase) 當(dāng)事件到達(dá)目標(biāo)節(jié)點(diǎn)時(shí),事件就進(jìn)入了目標(biāo)階段。事件在目標(biāo)節(jié)點(diǎn)上被觸發(fā),然后逆向回流,知道傳播到最外層的文檔節(jié)點(diǎn)。 對于多層嵌套的節(jié)點(diǎn),鼠標(biāo)和指針事件經(jīng)常會被定位到最里層的元素上。假設(shè),你在一個(gè)div元素上設(shè)置了click的監(jiān)聽函數(shù),而用戶點(diǎn)擊在了這個(gè)div元素內(nèi)部的p元素上,那么p元素就是這個(gè)時(shí)間的目標(biāo)元素。事件冒泡讓我們可以在這個(gè)div或者更上層的元素上監(jiān)聽click事件,并且時(shí)間傳播過程中觸發(fā)回調(diào)函數(shù)。 冒泡階段(Bubble Phase) 事件在目標(biāo)事件上觸發(fā)后,并不在這個(gè)元素上終止。它會隨著DOM樹一層層向上冒泡,直到到達(dá)最外層的根節(jié)點(diǎn),一直向上傳播,直到document對象。也就是說,同一事件會一次在目標(biāo)節(jié)點(diǎn)的父節(jié)點(diǎn),父節(jié)點(diǎn)的父節(jié)點(diǎn)...直到最外層的節(jié)點(diǎn)上觸發(fā)。 絕大多數(shù)事件是會冒泡的,但并非所有的。 二.事件代理(event delegation)在JavaScript中,經(jīng)常會碰到要監(jiān)聽列表中多項(xiàng)li的情形,假設(shè)我們有一個(gè)列表如下: <ul id="list"> <li id="item1">item1</li> <li id="item2">item2</li> <li id="item3">item3</li> <li id="item4">item4</li> </ul> 如果我們要實(shí)現(xiàn)以下功能:當(dāng)鼠標(biāo)點(diǎn)擊某一li時(shí),alert輸出該li的內(nèi)容,我們通常的寫法是這樣的:
window.onload=function(){ var ulNode=document.getElementById("list"); var liNodes=ulNode.childNodes||ulNode.children; for(var i=0;i<liNodes.length;i++){ liNodes[i].addEventListener('click',function(e){ alert(e.target.innerHTML); },false); } } 由上可以看出來,假如不停的刪除或添加li,則function()也要不停的更改操作,易出錯,因此推薦使用事件代理。 在傳統(tǒng)的事件處理中,你按照需要為每一個(gè)元素添加或者是刪除事件處理器。然而,事件處理器將有可能導(dǎo)致內(nèi)存泄露或者是性能下降——你用得越多這種風(fēng)險(xiǎn)就越大。JavaScript事件代理則是一種簡單的技巧,通過它你可以把事件處理器添加到一個(gè)父級元素上,這樣就避免了把事件處理器添加到多個(gè)子級元素上。 事件代理機(jī)制事件代理用到了兩個(gè)在JavaSciprt事件中兩個(gè)特性:事件冒泡以及目標(biāo)元素。使用事件代理,我們可以把事件處理器添加到一個(gè)元素上,等待一個(gè)事件從它的子級元素里冒泡上來,并且可以得知這個(gè)事件是從哪個(gè)元素開始的。 事件代理實(shí)現(xiàn)例如,有一個(gè)table元素,ID是“report”,我們?yōu)檫@個(gè)表格添加一個(gè)事件處理器以調(diào)用editCell函數(shù)。 第一步,找到目標(biāo)元素 editCell函數(shù)需要判斷傳到table來的事件的目標(biāo)元素??紤]到我們要寫的幾個(gè)函數(shù)中都有可能用到這一功能,所以我們把它單獨(dú)放到一個(gè)名為getEventTarget的函數(shù)中: function getEventTarget(e) { e = e || window.event;//事件對象 return e.target || e.srcElement; } 在IE里目標(biāo)元素放在srcElemnt屬性中,而在其它瀏覽器里則是target屬性。 第二步,判斷目標(biāo)元素,進(jìn)行相關(guān)操作 接下來就是editCell函數(shù)了,這個(gè)函數(shù)調(diào)用到了getEventTarget函數(shù)。一旦我們得到了目標(biāo)元素,剩下的事情就是看看它是否是我們所需要的那個(gè)元素了。 function editCell(e){ var target = getEventTarget(e); if(target.tagName.toLowerCase() =='td') { // DO SOMETHING WITH THE CELL } } 由上敘述,我們可以使用事件代理來實(shí)現(xiàn)本小結(jié)開始對每一個(gè)li的監(jiān)聽。第三種方法,代碼如下: window.onload=function(){ e = e || window.event; target = e.target || e.srcElement; var ulNode=document.getElementById("list"); ulNode.addEventListener('click',function(e){ if(target&&target.nodeName.toUpperCase()=="LI"){/*判斷目標(biāo)事件是否為li*/ alert(e.target.innerHTML); } },false); }; 注:
三.事件兼容處理現(xiàn)代綁定中W3C 使用的是:addEventListener 和removeEventListener。IE 使用的是attachEvent 和detachEvent。我們知道IE 的這兩個(gè)問題多多,并且伴隨內(nèi)存泄漏。所以,解決這些問題非常有必要。 那么我們希望解決非IE 瀏覽器事件綁定哪些問題呢?
設(shè)計(jì)原理1.通過使用傳統(tǒng)事件綁定對IE 進(jìn)行封裝,模擬現(xiàn)代事件綁定(不使用attachEvent/detachEvent)。 2.把IE常用的Event對象配對到W3C中去 //跨瀏覽器添加事件綁定 function addEvent(obj, type, fn) { if (typeof obj.addEventListener != 'undefined') { obj.addEventListener(type, fn, false); } else { //創(chuàng)建一個(gè)存放事件的哈希表(散列表) if (!obj.events) obj.events = {}; //第一次執(zhí)行時(shí)執(zhí)行 if (!obj.events[type]) { //創(chuàng)建一個(gè)存放事件處理函數(shù)的數(shù)組 obj.events[type] = []; //把第一次的事件處理函數(shù)先儲存到第一個(gè)位置上 if (obj['on' + type]) obj.events[type][0] = fn; } else { //同一個(gè)注冊函數(shù)進(jìn)行屏蔽,不添加到計(jì)數(shù)器中 if (addEvent.equal(obj.events[type], fn)) return false; } //從第二次開始我們用事件計(jì)數(shù)器來存儲 obj.events[type][addEvent.ID++] = fn; //執(zhí)行事件處理函數(shù) obj['on' + type] = addEvent.exec; } } //為每個(gè)事件分配一個(gè)計(jì)數(shù)器 addEvent.ID = 1; //執(zhí)行事件處理函數(shù) addEvent.exec = function (event) { var e = event || addEvent.fixEvent(window.event); var es = this.events[e.type]; for (var i in es) { es[i].call(this, e); } }; //同一個(gè)注冊函數(shù)進(jìn)行屏蔽 addEvent.equal = function (es, fn) { for (var i in es) { if (es[i] == fn) return true; } return false; } //把IE常用的Event對象配對到W3C中去 addEvent.fixEvent = function (event) { event.preventDefault = addEvent.fixEvent.preventDefault; event.stopPropagation = addEvent.fixEvent.stopPropagation; event.target = event.srcElement; return event; }; //IE阻止默認(rèn)行為 addEvent.fixEvent.preventDefault = function () { this.returnValue = false; }; //IE取消冒泡 addEvent.fixEvent.stopPropagation = function () { this.cancelBubble = true; }; //跨瀏覽器刪除事件 function removeEvent(obj, type, fn) { if (typeof obj.removeEventListener != 'undefined') { obj.removeEventListener(type, fn, false); } else { if (obj.events) { for (var i in obj.events[type]) { if (obj.events[type][i] == fn) { delete obj.events[type][i]; } } } } }
知識說明: 添加、移除事件,代碼如下: var addEvent = document.addEventListener ? function(elem,type, listener, useCapture) { elem.addEventListener(type, listener, useCapture); }: function(elem, type, listener, useCapture) { elem.attachEvent('on' + type, listener); }; var delEvent = document.removeEventListener ? function(elem, type, listener, useCapture) { elem.removeEventListener(type, listener, useCapture) }: function(elem, type, listener, useCapture) { elem.detachEvent('on' + type, listener); }; 阻止事件繼續(xù)傳播,代碼如下: function stopEvent (evt) { var evt = evt || window.event; if (evt.stopPropagation) { evt.stopPropagation(); } else { evt.cancelBubble = true; } } 取消默認(rèn)行為,代碼如下: function stopEvent (evt) { var evt = evt || window.event; if (evt.preventDefault) { evt.preventDefault(); } else { evt.returnValue = false; } } 四.事件類型事件類型有:UI(用戶界面)事件,用戶與頁面上元素交互時(shí)觸發(fā) ;焦點(diǎn)事件:當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā) ; 文本事件:當(dāng)在文檔中輸入文本時(shí)觸發(fā);鍵盤事件:當(dāng)用戶通過鍵盤在頁面上執(zhí)行操作時(shí)觸發(fā);鼠標(biāo)事件:當(dāng)用戶通過鼠標(biāo)在頁面上執(zhí)行操作時(shí)觸發(fā);滾輪事件:當(dāng)使用鼠標(biāo)滾輪(或類似設(shè)備)時(shí)觸發(fā)。它們之間是繼承的關(guān)系,如下圖: 1. 常用:window、image。例如設(shè)置默認(rèn)的圖片: <img src="photo" alt="photo.jpg" onerror="this.src='defualt.jpg'"> 2. 3. 4. 5. 6.
7. 詳細(xì)使用,請參考js事件手冊。如:http://www.w3school.com.cn/jsref/dom_obj_event.asp ------------------------------------------------------------------------------------------------------------------------------------- 完 轉(zhuǎn)載需注明轉(zhuǎn)載字樣,標(biāo)注原作者和原博文地址。 更多閱讀: http://blog.sina.com.cn/s/blog_5f54f0be0100cy49.html http://www.cnblogs.com/luhangnote/archive/2012/08/16/2642657.html |
|