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。

 

posted @ 2012-05-13 11:10  凉粉侠  阅读(1852)  评论(0)    收藏  举报