今天抽時間寫了一部分Event處理方面的函式愈發的覺得jQuery的優秀,自己前期的想法太粗糙,造成後面這些函式引數很多,操作很很不直觀,看樣子是要重構的節奏,還好小夥兒伴們安慰,架構都是改出來的。繼續探索吧
瀏覽器相容性
寫Event處理的庫函式一個難點就在於瀏覽器相容性問題,在IE低版本瀏覽器中事件物件始終在window.event屬性中,而在其它瀏覽器中event會作為事件處理程式中作為第一個引數傳入。而且其Event物件的屬性和方法也有諸多差異,在JavaScript與HTML互動——事件中基本有所總結,不過還是抄一段關於事件處理程式繫結方面的差異
1. 引數個數不相同,這個最直觀,addEventListener有三個引數,attachEvent只有兩個,attachEvent新增的事件處理程式只能發生在冒泡階段,addEventListener第三個引數可以決定新增的事件處理程式是在捕獲階段還是冒泡階段處理(我們一般為了瀏覽器相容性都設定為冒泡階段)
2. 第一個引數意義不同,addEventListener第一個引數是事件型別(比如click,load),而attachEvent第一個引數指明的是事件處理函式名稱(onclick,onload)
3. 事件處理程式的作用域不相同,addEventListener得作用域是元素本身,this是指的觸發元素,而attachEvent事件處理程式會在全域性變數內執行,this是window
4. 為一個事件新增多個事件處理程式時,執行順序不同,addEventListener新增會按照新增順序執行,而attachEvent新增多個事件處理程式時順序無規律(新增的方法少的時候大多是按新增順序的反順序執行的,但是新增的多了就無規律了),所以新增多個的時候,不依賴執行順序的還好,若是依賴於函式執行順序,最好自己處理,不要指望瀏覽器
最簡單的四個
先寫四個最簡單的
- getEvent:獲取事件物件
- getTarget:獲取事件源物件
- preventDefault:阻止事件預設行為
- stopPropagation:阻止事件冒泡
(function (window) { var ssLib = { getEvent: function (e) { return e ? e : window.event; }, getTarget: function (e) { var e = this.getEvent(e); return e.target || e.srcElement; }, preventDefault: function (e) { var e = this.getEvent(e); if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } }, stopPropagation: function (e) { var e = this.getEvent(e); if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } } }; window.ssLib = window.ss = ssLib; })(window);
程式碼很簡單,相信聰明的小夥兒伴們一看就懂,就不多做解釋了
addEvent/removeEvent
- addEvent:為元素繫結事件處理程式
- removeEvent:移除元素事件處理程式
addEvent: function (element, type, handler, key) { var key = key || handler; if (element[type + key]) return false; if (typeof element.addEventListener != "undefined") { element[type + key] = handler; element.addEventListener(type, handler, false); } else { element['e' + type + key] = handler; element[type + key] = function () { element['e' + type + key](window.event); }; element.attachEvent('on' + type, element[type + key]); } return true; }, removeEvent: function (element, type, key) { if (!element[type + key]) return false; if (typeof element.removeEventListener != "undefined") { element.removeEventListener(type, element[type + key], false); } else { element.detachEvent("on" + type, element[type + key]); element['e' + type + key] = null; } element[type + key] = null; return true; },
這兩個函式相容性寫法有很多,結合了很多大牛的寫法後我用的上面版本,這麼些看似很複雜,實際上主要解決了上面提到的、除了多個事件處理程式執行順序問題的瀏覽器相容性問題,比較難看懂的IE繫結部分就是為了處理this而寫的。
在使用的時候,可以顯示傳入一個key用於內部識別繫結函式,如果不繫結則使用handler本身作為key,所以可以這麼用
ssLib.addEvent(element,'click',function(){},'test'); ssLib.removeEvent(element,'click','test'); function handler(){} ssLib.addEvent(element,'click',handler); ssLib.removeEvent(element,'click',handler);
on/off
這個是看到jQuery的on/delegate和YUI 的delegate後萌發的想法,不過平時老用人家的沒自己寫過,倉促寫了一個,感慨頗多,還是jQuery好使
on: function (parent, type, handler,validater,key) { var _key=key || handler; parent['on'+type+_key]=function (e) { var target = ssLib.getTarget(e); var isFire = validater(target); if (isFire) { target[type + _key] = handler; target[type + _key](e); } }; ssLib.addEvent(parent, type,parent['on'+type+_key] , key); }, off: function (parent, type, key) { if(typeof key=='function'){ ssLib.removeEvent(parent, type, parent['on'+type+key]); }else{ ssLib.removeEvent(parent, type, key); } parent['on'+type+key]=null; }
寫法和剛才類似,不停的繞來繞去也是解決this問題,可以這麼用
<div id="test"> <div id="t1">T1</div> <div id="t2">T2</div> </div>
var parent = document.getElementById('test'); var handler=function () { alert(this.id); } var validater=function (obj) { if(obj.parentNode.id=='test'){ return true; }else{ return false; } } ss.on(parent, 'click', handler, validater); ss.off(parent,'click',handler); ss.on(parent, 'click', handler, validater,'delegate'); ss.off(parent,'click','delegate');
ready
用過jQuery的同學肯定知道這個函式,寫自己庫的時候是各種驚羨啊,於是乎加到了自己的庫裡
ready: function (fn) { if (document.addEventListener) { document.addEventListener("DOMContentLoaded", function () { document.removeEventListener("DOMContentLoaded", arguments.callee, false);// 防止多次呼叫 fn(); }, false); } else if (document.addEvent) { var doc = window.document, done = false; // only fire once var init = function () { if (!done) { done = true; fn(); } }; // polling for no errors (function () { try { // throws errors until after ondocumentready doc.documentElement.doScroll('left');// 文件載入完成後此方法可用 } catch (e) { setTimeout(arguments.callee, 50); return; } // no errors, fire init(); })(); // trying to always fire before onload doc.onreadystatechange = function () { if (doc.readyState == 'complete') { doc.onreadystatechange = null; init(); } }; } }
要想看懂上面程式碼最好看看An alternative for DOMContentLoaded on Internet Explorer
在現代瀏覽器中文件載入完後會觸發“DOMContentLoaded”事件,而在底版本IE中文件載入完成後會doScroll方法會生效
Event部分原始碼
(function (window) { var ssLib = { getEvent: function (e) { return e ? e : window.event; }, getTarget: function (e) { var e = this.getEvent(e); return e.target || e.srcElement; }, preventDefault: function (e) { var e = this.getEvent(e); if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } }, stopPropagation: function (e) { var e = this.getEvent(e); if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } }, addEvent: function (element, type, handler, key) { var key = key || handler; if (element[type + key]) return false; if (typeof element.addEventListener != "undefined") { element[type + key] = handler; element.addEventListener(type, handler, false); } else { element['e' + type + key] = handler; element[type + key] = function () { element['e' + type + key](window.event); }; element.attachEvent('on' + type, element[type + key]); } return true; }, removeEvent: function (element, type, key) { if (!element[type + key]) return false; if (typeof element.removeEventListener != "undefined") { element.removeEventListener(type, element[type + key], false); } else { element.detachEvent("on" + type, element[type + key]); element['e' + type + key] = null; } element[type + key] = null; return true; }, ready: function (fn) { if (document.addEventListener) { document.addEventListener("DOMContentLoaded", function () { document.removeEventListener("DOMContentLoaded", arguments.callee, false); fn(); }, false); } else if (document.attachEvent) { var doc = window.document, done = false; // only fire once var init = function () { if (!done) { done = true; fn(); } }; // polling for no errors (function () { try { // throws errors until after ondocumentready doc.documentElement.doScroll('left'); } catch (e) { setTimeout(arguments.callee, 50); return; } // no errors, fire init(); })(); // trying to always fire before onload doc.onreadystatechange = function () { if (doc.readyState == 'complete') { doc.onreadystatechange = null; init(); } }; } }, on: function (parent, type, handler, validater, key) { var _key = key || handler; parent['on' + type + _key] = function (e) { var target = ssLib.getTarget(e); var isFire = validater(target); if (isFire) { target[type + _key] = handler; target[type + _key](e); } }; ssLib.addEvent(parent, type, parent['on' + type + _key], key); }, off: function (parent, type, key) { if (typeof key == 'function') { ssLib.removeEvent(parent, type, parent['on' + type + key]); } else { ssLib.removeEvent(parent, type, key); } parent['on' + type + key] = null; } }; window.ssLib = window.ss = ssLib; })(window);