jQuery事件处理一瞥
以前部门一直都是使用一个名为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 onclick="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。
浙公网安备 33010602011771号