以前部门一直都是使用一个名为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 properties var 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 necessary if ( !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 necessary if ( !event.relatedTarget && event.fromElement ) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } // Calculate pageX/Y if missing and clientX/Y available if ( 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 events if ( !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 it if ( !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' keyword if ( !this.preventDefault ) { return new jQuery.Event( src ); } // Event object if ( 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 value this.timeStamp = now(); // Mark it as fixed this[ 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 event if ( 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 event if ( 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.handleliveHandlerJQ没有将其单独成一个方法。暂时写到这里,等以后有时间再慢慢研究这个currentTarget。