摘要:本文章需要一些前置知識事件基礎(chǔ)知識對象詳解圍繞著如何更好地實(shí)現(xiàn)一個(gè)跨瀏覽器的事件處理小型庫展開討論。處理垃圾回收過濾觸發(fā)或刪除一些處理程序解綁特定類型的所有事件克隆事件處理程序依照這樣的一個(gè)思路,我們來一步步實(shí)現(xiàn)這樣一個(gè)模塊。
本文章需要一些前置知識
事件基礎(chǔ)知識
event對象詳解
圍繞著如何更好地實(shí)現(xiàn)一個(gè)跨瀏覽器的事件處理小型庫展開討論。
1. 初步實(shí)現(xiàn)在《JavaScript高級程序設(shè)計(jì)》中提供了一個(gè)EventUtil的對象,里面實(shí)現(xiàn)了一個(gè)跨瀏覽器的事件綁定的API
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.removeEventListener(type, handler); } else if (el.detachEvent) { el.detachEvent("on" + type, handler); } else { el["on" + type] = null; } } }
這是實(shí)現(xiàn)其實(shí)較為的簡單直觀,但是對于IE瀏覽器的處理其實(shí)有不好的地方,例如我們都知道attachEvent()中的事件處理程序會在全局作用域下執(zhí)行,那么函數(shù)中的this就會指向window對象,這是一個(gè)問題,當(dāng)然我們也可以對handler進(jìn)行處理,綁定handler的函數(shù)作用域。此外,EventUtil并沒有對event對象進(jìn)行處理,因此傳入handler的event也需要做兼容性處理,在封裝方面做的就不好,編寫handler時(shí)需要注意的地方就比較多。
var handler = function (event) { // 對event對象做兼容性處理,例如獲取target等 }; // 綁定函數(shù)作用域 handler = handler.bind(el);2. 更好的實(shí)現(xiàn)
下面是Dean Edward的實(shí)現(xiàn),這也是jquery所借鑒的,拋棄掉attachEvent方法,直接使用跨瀏覽器的實(shí)現(xiàn)方式,即el.onXXX = handler,這種方式的確定就是無法綁定多個(gè),會進(jìn)行覆蓋,但是可以利用一些技巧來彌補(bǔ)。
// written by Dean Edwards, 2005 // with input from Tino Zijdel, Matthias Miller, Diego Perini // http://dean.edwards.name/weblog/2005/10/add-event/ function addEvent(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else { // assign each event handler a unique ID if (!handler.$$guid) handler.$$guid = addEvent.guid++; // create a hash table of event types for the element if (!element.events) element.events = {}; // create a hash table of event handlers for each element/event pair var handlers = element.events[type]; if (!handlers) { handlers = element.events[type] = {}; // store the existing event handler (if there is one) if (element["on" + type]) { handlers[0] = element["on" + type]; } } // store the event handler in the hash table handlers[handler.$$guid] = handler; // assign a global event handler to do all the work element["on" + type] = handleEvent; } }; // a counter used to create unique IDs addEvent.guid = 1; function removeEvent(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else { // delete the event handler from the hash table if (element.events && element.events[type]) { delete element.events[type][handler.$$guid]; } } }; function handleEvent(event) { var returnValue = true; // grab the event object (IE uses a global event object) event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event); // get a reference to the hash table of event handlers var handlers = this.events[event.type]; // execute each event handler for (var i in handlers) { this.$$handleEvent = handlers[i]; if (this.$$handleEvent(event) === false) { returnValue = false; } } return returnValue; }; function fixEvent(event) { // add W3C standard event methods event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; return event; }; fixEvent.preventDefault = function() { this.returnValue = false; }; fixEvent.stopPropagation = function() { this.cancelBubble = true; };
這段代碼其實(shí)是對IE瀏覽器事件綁定的一個(gè)修補(bǔ),特別是舊版本的(IE8及更早的版本)。jquery借鑒了這樣的一個(gè)思路,寫出了兼容各個(gè)瀏覽器的event模塊。
3. jquery的實(shí)現(xiàn)思路在《JavaScript忍者秘籍》中,給出了一個(gè)更加高級的實(shí)現(xiàn),他使用一個(gè)中間事件處理程序,并將所有的處理程序都保存在一個(gè)多帶帶的對象上,最大化地控制處理的過程,這樣做有幾個(gè)好處:
規(guī)范處理程序的上下文,這個(gè)指的是作用域的問題,正常來說,元素的事件處理程序的上下文應(yīng)該就是元素本身,即this === el為true。
修復(fù)Event對象的屬性,通過兼容性的處理,來達(dá)到與標(biāo)準(zhǔn)無異。
處理垃圾回收
過濾觸發(fā)或刪除一些處理程序
解綁特定類型的所有事件
克隆事件處理程序
依照這樣的一個(gè)思路,我們來一步步實(shí)現(xiàn)這樣一個(gè)模塊。
3.1 修復(fù)Event對象的屬性修復(fù)主要針對一些重要的屬性進(jìn)行修復(fù),結(jié)合上一節(jié)的內(nèi)容,有以下代碼:
function fixEvent(event) { function returnTrue () {return true;} function returnFalse () {return false;} if(!event || !event.stopPropagation) { // 判斷是否需要修復(fù) var old = event || window.event; // IE的event從window對象中獲取 event = {}; // 復(fù)制原有的event對象的屬性 for(var prop in old) { event[prop] = old[prop]; } // 處理target if(!event.target) { event.target = event.srcElement || document; } // 處理relatedTarget event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; // 處理preventDefault event.preventDefault = function () { event.returnValue = false; // 標(biāo)識,event對象是否調(diào)用了preventDefault函數(shù) event.isDefaultPrevented = returnTrue; } /* 可以調(diào)用event.isDefaultPrevented()來查看是否調(diào)用event.preventDefault */ event.isDefaultPrevented = returnFalse; event.stopPropagation = function () { event.cancelBubble = true; event.isPropagationStopped = returnTrue; } event.isPropagationStopped = returnFalse; // 阻止事件冒泡,并且阻止執(zhí)行其他的事件處理程序 // 借助標(biāo)識位,可以在后面進(jìn)行handlers隊(duì)列處理的時(shí)候使用 event.stopImmediatePropagation = function () { event.isImmediatePropagationStopped = returnTrue; event.stopPropagation(); } event.isImmediatePropagationStopped = returnFalse; // 鼠標(biāo)坐標(biāo),返回文檔坐標(biāo) if(event.clientX != null){ var doc = document.documentElement, body = document.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } event.which = event.charCode || event.keyCode; // 鼠標(biāo)點(diǎn)擊模式 left -> 0 middle -> 1 right -> 2 if(event.button != null){ event.button = (event.button & 1 ? 0 : (event.button & 4 ? 1 : (event.button & 2 ? 2 : 0))); } } return event; }3.2 中央對象保存dom元素信息
這個(gè)的目的是為了給元素建立一個(gè)映射,標(biāo)識元素和存儲相關(guān)聯(lián)的信息(事件類型和對應(yīng)的事件處理程序),在jquery里面使用的是selector,在《JavaScript忍者秘籍》中,使用的是guid。
var cache = {}, guidCounter = 1, expando = "data" + (new Date).getTime(); function getData(el) { var guid = el[expando]; if(!guid){ guid = el[expando] = guidCounter++; cache[guid] = {}; } return cache[guid]; } function removeData(el) { var guid = el[expando]; if(!guid) return; delete cache[guid]; try { delete el[expando]; } catch(e){ if(el.removeAttribute){ el.removeAttribute(expando); } } }3.3 綁定事件處理程序
var nextGuid = 1; function addEvent(el, type, fn) { var data = getData(el); if(!data.handlers)data.handlers = {}; if(!data.handlers[type])data.handlers[type] = []; // 給事件處理程序賦予guid,便于后面刪除 if(!fn.guid)fn.guid = nextGuid++; data.handlers[type].push(fn); // 為該元素的事件綁定統(tǒng)一的回調(diào)處理程序 if(!data.dispatcher) { // 是否啟用data.dispatcher data.disabled = false; data.dispatcher = function (event) { if(data.disabled)return; event = fixEvent(event); var handlers = data.handlers[event.type]; if(handlers) { for(var i = 0, len = handlers.length; i < len; i++){ handlers[i].call(el, event); } } }; } // 將統(tǒng)一的回調(diào)處理程序注冊到,僅在第一次注冊的時(shí)候需要 if(data.handlers.length === 1){ if(el.addEventListener){ el.addEventListener(type, data.dispatcher, false); } else (el.attachEvent) { el.attachEvent("on" + type, data.dispatcher); } } }3.4 清理資源
綁定了事件,就還需要一個(gè)解綁事件,因?yàn)槲覀兪褂玫氖俏刑幚沓绦騺砜刂铺幚砹鞒蹋皇侵苯咏壎ㄌ幚沓绦颍砸膊荒苤苯邮褂脼g覽器提供的解綁函數(shù)來處理。在這里,我們需要手動來清理一些資源,清理的順序從小到大。
function isEmpty(o){ for(var prop in o){ return false; } return true; } function tidyUp(el, type) { var data = getData(el); // 清理el的type事件的回調(diào)程序 if(data.handlers[type].length === 0) { delete data.handlers[type]; if(el.removeEventListener){ el.removeEventListener(type, data.dispatcher, false); } else if(el.detachEvent){ el.detachEvent("on" + type, data.dispatcher); } } // 判斷是否還有其他類型的事件處理程序,如果沒有則進(jìn)一步清除 if(isEmpty(data.handlers)){ delete data.handlers; delete data.dispatcher; } // 判斷是否還需要data對象 if(isEmpty(data)) { removeData(el); } }3.5 解綁事件處理程序
為了盡可能保持靈活,提供了以下的功能
將一個(gè)元素的所有綁定事件進(jìn)行解綁
removeEvent(el);
將一個(gè)元素特定類型的所有事件進(jìn)行解綁
removeEvent(el, "click");
將一個(gè)元素的特定處理程序進(jìn)行解綁
removeEvent(el, "click", handler);
function removeEvent(el, type, fn) { var data = getData(el); if(!data.handlers)return; var removeType = function(t) { data.handlers[t] = []; tidyUp(el, t); }; // 刪除所有的處理程序 if(!type){ for(var t in data.handlers){ removeType(t); } return; } var handlers = data.handlers[type]; if(!handlers)return; // 刪除特定類型的所有事件處理程序 if(!fn){ removeType(type); return; } // 刪除特定的事件處理程序,這個(gè)時(shí)候根據(jù)guid來進(jìn)行刪除 // 這里需要考慮的就是可能一個(gè)事件處理程序被綁定到一個(gè)事件類型多次 // 因此,這里需要用到handlers.length,刪除的時(shí)候,需要n-- if(fn.guid) { for(var n = 0; n < handlers.length; n++){ if(handlers[n].guid === fn.guid){ handlers.splice(n--, 1); } } } // 返回之前進(jìn)行資源清理 tidyUp(el, type); }
到這里,我們就得到一個(gè)既保證通用性又保證性能的事件監(jiān)聽處理模塊,然而事件的知識并不僅僅這么一點(diǎn),本章節(jié)的內(nèi)容將會繼續(xù)出現(xiàn)在接下來的幾個(gè)小節(jié),一起構(gòu)建一個(gè)完整的event體系的代碼庫。
4. 參考《JavaScript高級程序設(shè)計(jì)》
《JavaScript忍者秘籍》
addEvent
5. 來源個(gè)人博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/91274.html
摘要:三級事件處理程序級事件定義了兩個(gè)方法,分別用于處理指定和刪除事件處理程序的操作和,他們都接收三個(gè)參數(shù)要處理的事件名作為事件處理程序的函數(shù)一個(gè)布爾值。布爾值如果是表示在捕獲階段調(diào)用事件處理程序,如果是表示在冒泡階段調(diào)用事件處理程序。 前言:擼完CSS-DOM緊接著來擼DOM事件,事件總結(jié)完成后我要開始總結(jié)動畫,然后用純JS實(shí)現(xiàn)一個(gè)輪播圖,前路漫漫,還有各種框架等著我~~~本篇主要內(nèi)容有:...
摘要:在事件處理,事件對象,阻止事件的傳播等方法或?qū)ο蟠嬖谥鵀g覽器兼容性問題,開發(fā)過程中最好編寫成一個(gè)通用的事件處理工具。上面的中事件的執(zhí)行都發(fā)生了目標(biāo)階段事件對象的屬性用來表示事件處理發(fā)生在事件流哪個(gè)階段。 最近在閱讀javascript高級程序設(shè)計(jì),事件這一塊還是有很多東西要學(xué)的,就把一些思考和總結(jié)記錄下。在事件處理,事件對象,阻止事件的傳播等方法或?qū)ο蟠嬖谥鵀g覽器兼容性問題,開發(fā)過程中...
摘要:前端知識點(diǎn)總結(jié)什么是第三方的極簡化的操作的函數(shù)庫第三方下載極簡化是操作的終極簡化個(gè)方面增刪改查事件綁定動畫效果操作學(xué)習(xí)還是在學(xué),只不過簡化了函數(shù)庫中都是函數(shù),用函數(shù)來解決一切問題為什么使用操作的終極簡化解決了大部分瀏覽器兼容性問題凡是讓用的 前端知識點(diǎn)總結(jié)——JQ 1.什么是jQuery: jQuery: 第三方的極簡化的DOM操作的函數(shù)庫 第三方: 下載 極簡化: 是DOM操作的...
摘要:頁面發(fā)起一個(gè)到服務(wù)器的請求,然后服務(wù)器一直保持連接打開,直到有數(shù)據(jù)可發(fā)送。 Ajax與Comet XMLHttpRequest對象 IE5是第一款引入XHR對象的瀏覽器,在IE5中,XHR對象是通過MSXML庫中的一個(gè)ActiveX對象實(shí)現(xiàn)的 //適用于 IE7 之前的版本 function createXHR(){ if (typeof arguments.callee.acti...
閱讀 952·2021-09-26 09:55
閱讀 3205·2021-09-22 15:36
閱讀 2989·2021-09-04 16:48
閱讀 3147·2021-09-01 11:41
閱讀 2598·2019-08-30 13:49
閱讀 1497·2019-08-29 18:46
閱讀 3553·2019-08-29 17:28
閱讀 3433·2019-08-29 14:11