以前部门一直都是使用一个名为QTT的JS框架。最近老大提出要转用jQuery框架,需要将旧框架的一些JQ没有实现的功能移植到JQ中去。当我移植到event库的时候,以下是其代码:
QTT.event = {KEYS : {BACKSPACE : 8,TAB : 9,RETURN : 13,ESC : 27,SPACE : 32,LEFT : 37,UP : 38,RIGHT : 39,DOWN : 40,DELETE : 46},extendType : /(click|mousedown|mouseover|mouseout|mouseup|mousemove|scroll|contextmenu|resize)/i,_eventListDictionary : {},_fnSeqUID : 0,_objSeqUID : 0,addEvent : function(obj, eventType, fn, argArray) {var cfn = fn,res = false, l;if (!obj) {return res;}if (!obj.eventsListUID) {obj.eventsListUID = "e" + (++QTT.event._objSeqUID);QTT.event._eventListDictionary[obj.eventsListUID] = {};}l = QTT.event._eventListDictionary[obj.eventsListUID];if (!fn.__elUID) {fn.__elUID = "e" + (++QTT.event._fnSeqUID) + obj.eventsListUID;}if (!l[eventType]) {l[eventType] = {};}if(typeof(l[eventType][fn.__elUID])=='function'){return false;}if (QTT.event.extendType.test(eventType)) {var argArray = argArray || [];cfn = function(e) {
//吐槽音:为啥这里不用obj而用了null, 害得我在回调事件中不能用this -_-!!return fn.apply(null, ([QTT.event.getEvent(e)]).concat(argArray));};}if (obj.addEventListener) {obj.addEventListener(eventType, cfn, false);res = true;} else if (obj.attachEvent) {res = obj.attachEvent("on" + eventType, cfn);} else {res = false;}if (res) {l[eventType][fn.__elUID] = cfn;}return res;},removeEvent : function(obj, eventType, fn) {var cfn = fn, res = false, l;if (!obj) {return res;}if (!cfn) {return QTT.event.purgeEvent(obj, eventType);}if (!obj.eventsListUID) {obj.eventsListUID = "e" + (++QTT.event._objSeqUID);QTT.event._eventListDictionary[obj.eventsListUID] = {};}l = QTT.event._eventListDictionary[obj.eventsListUID];if (!fn.__elUID) {fn.__elUID = "e" + (++QTT.event._fnSeqUID) + obj.eventsListUID;}if (!l[eventType]) {l[eventType] = {};}if (QTT.event.extendType.test(eventType)&&l[eventType]&&l[eventType][fn.__elUID]) {cfn = l[eventType][fn.__elUID];}if (obj.removeEventListener) {obj.removeEventListener(eventType, cfn, false);res = true;} else if (obj.detachEvent) {obj.detachEvent("on" + eventType, cfn);res = true;} else {return false;}if (res && l[eventType]) {delete l[eventType][fn.__elUID];}return res;},purgeEvent : function(obj, type) {var l;if (obj.eventsListUID && (l=QTT.event._eventListDictionary[obj.eventsListUID]) && l[type]) {for (var k in l[type]) {if (obj.removeEventListener) {obj.removeEventListener(type, l[type][k],false);} else if (obj.detachEvent) {obj.detachEvent('on' + type, l[type][k]);}}}if (obj['on' + type]) {obj['on' + type] = null;}if (l) {l[type] = null;delete l[type];}return true;},getEvent : function(evt) {var evt = evt || window.event;if (!evt && !QTT.userAgent.ie) {var c = this.getEvent.caller,cnt = 1;while (c) {evt = c.arguments[0];if (evt && Event == evt.constructor) {break;}else if(cnt > 32){break;}c = c.caller;cnt++;}}return evt;},getButton : function(evt) {var e = QTT.event.getEvent(evt);if (!e) {return -1}if (QTT.userAgent.ie) {return e.button - Math.ceil(e.button / 2);} else {return e.button;}},getTarget : function(evt) {var e = QTT.event.getEvent(evt);if (e) {return e.target || e.srcElement;} else {return null;}},getCurrentTarget : function(evt) {var e = QTT.event.getEvent(evt);if (e) { return e.currentTarget || document.activeElement;} else {return null;}},cancelBubble : function(evt) {evt = QTT.event.getEvent(evt);if (!evt) {return false}if (evt.stopPropagation) {evt.stopPropagation();} else {if (!evt.cancelBubble) {evt.cancelBubble = true;}}},preventDefault : function(evt) {evt = QTT.event.getEvent(evt);if (!evt) {return false}if (evt.preventDefault) {evt.preventDefault();} else {evt.returnValue = false;}},mouseX : function(evt) {evt = QTT.event.getEvent(evt);return evt.pageX || (evt.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));},mouseY : function(evt) {evt = QTT.event.getEvent(evt);return evt.pageY || (evt.clientY + (document.documentElement.scrollTop || document.body.scrollTop));},getRelatedTarget: function(ev) {ev = QTT.event.getEvent(ev);var t = ev.relatedTarget;if (!t) {if (ev.type == "mouseout") {t = ev.toElement;} else if (ev.type == "mouseover") {t = ev.fromElement;} else {}}return t;},bind : function(obj, method) {var args = Array.prototype.slice.call(arguments, 2);return function() {var _obj = obj || this;var _args = args.concat(Array.prototype.slice.call(arguments, 0));if (typeof(method) == "string") {if (_obj[method]) {return _obj[method].apply(_obj, _args);}} else {return method.apply(_obj, _args);}}}
};
以上方法中,addEvent、removeEvent、purgeEvent、bind这几个方法在JQ中有直接实现的。但好像在JQ的api中并没有跟getEvent、getButton、getTarget、getCurrentTarget、cancelBubble、preventDefault、mouseX、mouseY、getRelatedTarget这几个方法相关的事件。
但是JQ库这么强大,不可能没考虑这些通用函数的。既然API手册没有说,就只能翻出了jQuery的源码来看下了。
我在jQuery.event这个命名空间下发现了这个方法:
fix: function( event ) {if ( event[ expando ] ) {return event;}// store a copy of the original event object// and "clone" to set read-only propertiesvar originalEvent = event;event = jQuery.Event( originalEvent );for ( var i = this.props.length, prop; i; ) {prop = this.props[ --i ];event[ prop ] = originalEvent[ prop ];}// Fix target property, if necessaryif ( !event.target ) {event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either}// check if target is a textnode (safari)if ( event.target.nodeType === 3 ) {event.target = event.target.parentNode;}// Add relatedTarget, if necessaryif ( !event.relatedTarget && event.fromElement ) {event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;}// Calculate pageX/Y if missing and clientX/Y availableif ( event.pageX == null && 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);}// Add which for key eventsif ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {event.which = event.charCode || event.keyCode;}// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)if ( !event.metaKey && event.ctrlKey ) {event.metaKey = event.ctrlKey;}// Add which for click: 1 === left; 2 === middle; 3 === right// Note: button is not normalized, so don't use itif ( !event.which && event.button !== undefined ) {event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));}return event;
},
顿时廓然开朗。从功能上看,jQuery.event.fix() 的功能是修复event对象,让事件在各种浏览器下表现一致。它修复的事件属性包括:
1.event.target =====>被触发的对象,可替代QTT.event.getTarget()
2.event.relatedTarget =====>可替代QTT.event.getRelatedTarget()
3.event.pageX和event.pageY =====>事件触发时的鼠标位置,可替代QTT.event.mouseX()和QTT.event.mouseY()
4.event.which =====>事件触发时的按键,包括键盘和鼠标。可替代QTT.event.getButton()
5.event.metaKey =====>事件触发时有没按下ctrl键(Mac下是meta键)
值得注意的是上面的event.which,它既可以用来获得键盘输入,也可以获得鼠标输入。标准的button采用0,1,2表示鼠标的左,中,右键。jQuery的which则使用用1,2,3。
除了对事件属性的修复,它还对最关键的event对象进行了修复:
var originalEvent = event; event = jQuery.Event( originalEvent );
这个jQuery.Event的代码如下:
jQuery.Event = function( src ) {// Allow instantiation without the 'new' keywordif ( !this.preventDefault ) {return new jQuery.Event( src );}// Event objectif ( src && src.type ) {this.originalEvent = src;this.type = src.type;// Event type} else {this.type = src;}// timeStamp is buggy for some events on Firefox(#3843)// So we won't rely on the native valuethis.timeStamp = now();// Mark it as fixedthis[ expando ] = true;
};jQuery.Event.prototype = {preventDefault: function() {this.isDefaultPrevented = returnTrue;var e = this.originalEvent;if ( !e ) {return;}// if preventDefault exists run it on the original eventif ( e.preventDefault ) {e.preventDefault();}// otherwise set the returnValue property of the original event to false (IE)e.returnValue = false;},stopPropagation: function() {this.isPropagationStopped = returnTrue;var e = this.originalEvent;if ( !e ) {return;}// if stopPropagation exists run it on the original eventif ( e.stopPropagation ) {e.stopPropagation();}// otherwise set the cancelBubble property of the original event to true (IE)e.cancelBubble = true;},stopImmediatePropagation: function() {this.isImmediatePropagationStopped = returnTrue;this.stopPropagation();},isDefaultPrevented: returnFalse,isPropagationStopped: returnFalse,isImmediatePropagationStopped: returnFalse
};
可见经过处理后,event对象就拥有stopPropagation和preventDefault两个方法,可替代QTT.event.cancelBubble()和QTT.event.preventDefault()。
并且不需要通过QTT.event.getEvent()这样的方式去获取event对象了。
于是如果你需要在事件回调函数中阻止冒泡或者默认事件的话,这样写即可:
1.event.stopPropagation();
事件处理过程中,阻止了事件冒泡,但不会阻击默认行为(例如超链接的跳转)
2.return false;
事件处理过程中,阻止了事件冒泡,并且也阻止了默认行为
3.event.preventDefault();
事件处理过程中,不阻击事件冒泡,但阻击默认行为
还有一点,就是getEvent这个函数是干什么用的呢?先看一下其定义:
getEvent : function(evt) {var evt = evt || window.event;if (!evt && !QTT.userAgent.ie) {var c = this.getEvent.caller,cnt = 1;while (c) {evt = c.arguments[0];if (evt && Event == evt.constructor) {break;}else if(cnt > 32){break;}c = c.caller;cnt++;}}return evt;},
本来没看懂的,后来看了这篇文章后明白了。
例如<button οnclick="go(event)">click</button>假如函数写成go(),那FF这些浏览器就无法获取到event对象。这时就得通过caller调用父层函数的参数来获得event对象了。
经过一番分析挖掘之后,QTT.event类就只剩下QTT.event.KEYS和QTT.event.getCurrentTarget是需要移植的了。前者是一些常量,直接移植即可。而这个getCurrentTarget到底是什么呢?再往回翻代码看:
getCurrentTarget : function(evt) {var e = QTT.event.getEvent(evt);if (e) { return e.currentTarget || document.activeElement;} else {return null;}
}
部门的前辈对这个函数的解释是“返回获得焦点的对象”,跟target有什么不同呢?
在jQuery中搜索activeElement,没有;搜索currentTarget,发现在这几个方法中有用到:
jQuery.event.trigger
jQuery.event.handle
liveHandler
JQ没有将其单独成一个方法。暂时写到这里,等以后有时间再慢慢研究这个currentTarget。