/* * author:prk * date:2008-08-17 * comment:analyse of jquery event * */ jQuery.event = { // add 事件到一个元素上。 add : function(elem, types, handler, data) { if (elem.nodeType == 3 || elem.nodeType == 8)// 空白节点或注释 return; // IE不能传入window,先复制一下。 if (jQuery.browser.msie && elem.setInterval) elem = window; // 为handler分配一个全局唯一的Id if (!handler.guid) handler.guid = this.guid++; // 把data附到handler.data中 if (data != undefined) { var fn = handler; handler = this.proxy(fn, function() {// 唯一Id,wrap原始handler Fn return fn.apply(this, arguments); }); handler.data = data; } // 初始化元素的events。如果没有取到events中值,就初始化data: {} var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), // 如果没有取到handle中值,就初始化data: function() {....} handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function() { // 处理一个触发器的第二个事件和当page已经unload之后调用一个事件。 if (typeof jQuery != "undefined" && !jQuery.event.triggered) return jQuery.event.handle.apply(// arguments.callee.elem=handle.elem arguments.callee.elem, arguments); }); // 增加elem做为handle属性,防止IE由于没有本地Event而内存泄露。 handle.elem = elem; // 处理采用空格分隔多个事件名,如jQuery(...).bind("mouseover mouseout", fn); jQuery.each(types.split(/\s+/), function(index, type) { // 命名空间的事件,一般不会用到。 var parts = type.split("."); type = parts[0]; handler.type = parts[1]; // 捆绑到本元素type事件的所有处理函数 var handlers = events[type]; if (!handlers) {// 没有找到处理函数列表就初始化事件队列 handlers = events[type] = {}; // 如果type不是ready,或ready的setup执行返回false if (!jQuery.event.special[type] || jQuery.event.special[type].setup .call(elem, data) === false) { // 调用系统的事件函数来注册事件 if (elem.addEventListener)// FF elem.addEventListener(type, handle, false); else if (elem.attachEvent)// IE elem.attachEvent("on" + type, handle); } } // 把处理器的id和handler形式属性对的形式保存在handlers列表中, // 也存在events[type][handler.guid]中。 handlers[handler.guid] = handler; // 全局缓存这个事件的使用标识 jQuery.event.global[type] = true; }); // 防止IE内存泄露。 elem = null; }, guid : 1, global : {}, // 从元素中remove一个事件 remove : function(elem, types, handler) { if (elem.nodeType == 3 || elem.nodeType == 8) return; // 取出元素的events中Fn列表 var events = jQuery.data(elem, "events"), ret, index; if (events) { // remove所有的该元素的事件 .是命名空间的处理 if (types == undefined || (typeof types == "string" && types.charAt(0) == ".")) for (var type in events) this.remove(elem, type + (types || "")); else { // types, handler参数采用{type:xxx,handler:yyy}形式 if (types.type) { handler = types.handler; types = types.type; } // 处理采用空格分隔多个事件名 jQuery(...).unbind("mouseover mouseout", fn); jQuery .each(types.split(/\s+/), function(index, type) { // 命名空间的事件,一般不会用到。 var parts = type.split("."); type = parts[0]; if (events[type]) {// 事件名找到 if (handler)// handler传入,就remove事件名的这个处理函数 delete events[type][handler.guid];//guid的作用 else // remove这个事件的所有处理函数,带有命名空间的处理 for (handler in events[type]) if (!parts[1] || events[type][handler].type == parts[1]) delete events[type][handler]; // 如果没有该事件的处理函数存在,就remove事件名 for (ret in events[type])// 看看有没有? break; if (!ret) {// 没有 if (!jQuery.event.special[type]//不是ready || jQuery.event.special[type].teardown .call(elem) === false) {// type不等于ready if (elem.removeEventListener)// 在浏览器中remove事件名 elem.removeEventListener(type, jQuery.data(elem, "handle"), false); else if (elem.detachEvent) elem.detachEvent("on" + type, jQuery.data(elem, "handle")); } ret = null; delete events[type];// 在缓存中除去。 } } }); } // 不再使用,除去expando for (ret in events) break; if (!ret) { var handle = jQuery.data(elem, "handle"); if (handle) handle.elem = null; jQuery.removeData(elem, "events"); jQuery.removeData(elem, "handle"); } } }, trigger : function(type, data, elem, donative, extra) { data = jQuery.makeArray(data); if (type.indexOf("!") >= 0) {// 支持!的not的操作如!click,除click之后的所有 type = type.slice(0, -1);// 除最后一个字符? var exclusive = true; } if (!elem) {// 处理全局的fire事件 if (this.global[type]) jQuery.each(jQuery.cache, function() { // 从cache中找到所有注册该事件的元素,触发改事件的处理函数 if (this.events && this.events[type]) jQuery.event.trigger(type, data, this.handle.elem); }); } else {// 处理单个元素事件的fire事件 if (elem.nodeType == 3 || elem.nodeType == 8) return undefined; var val, ret, fn = jQuery.isFunction(elem[type] || null), // 我们是否要提交一个伪造的事件? event = !data[0] || !data[0].preventDefault; // 构建伪造的事件。 if (event) { data.unshift( {//存到数组中的第一个 type : type, target : elem, preventDefault : function() { }, stopPropagation : function() { }, timeStamp : now() }); data[0][expando] = true; // 不需要修正伪造事件 } //防止事件名出错 data[0].type = type; if (exclusive) data[0].exclusive = true; // 触发事件 var handle = jQuery.data(elem, "handle"); if (handle) val = handle.apply(elem, data); // Handle triggering native .onfoo handlers (and on links since we // don't call .click() for links) //处理触发.onfoo这样的本地处理方法,但是是对于links 's .click()不触发 if ((!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on" + type]&& elem["on" + type].apply(elem, data) === false) val = false; // Extra functions don't get the custom event object if (event) data.shift(); // 处理触发extra事件 if (extra && jQuery.isFunction(extra)) { //执行extra,同时把结果存到data中。 ret = extra.apply(elem, val == null ? data : data.concat(val)); // if anything is returned, give it precedence and have it // overwrite the previous value if (ret !== undefined) val = ret; } // 触发本地事件 if (fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click")) { this.triggered = true; try { elem[type](); //对于一些hidden的元素,IE会报错 } catch (e) { } } this.triggered = false; } return val; }, handle : function(event) { // 返回 undefined or false var val, ret, namespace, all, handlers; event = arguments[0] = jQuery.event.fix(event || window.event); // 命名空间处理 namespace = event.type.split("."); event.type = namespace[0]; namespace = namespace[1]; // all = true 表明任何 handler all = !namespace && !event.exclusive; // 找到元素的events中缓存的事件名的处理函数列表 handlers = (jQuery.data(this, "events") || {})[event.type]; for (var j in handlers) {// 每个处理函数执行 var handler = handlers[j]; // Filter the functions by class if (all || handler.type == namespace) { // 传入引用,为了之后删除它们 event.handler = handler; event.data = handler.data; ret = handler.apply(this, arguments);// 执行事件处理函数 if (val !== false) val = ret;// 只要有一个处理函数返回false,本函数就返回false. if (ret === false) {// 不执行浏览器默认的动作 event.preventDefault(); event.stopPropagation(); } } } return val; }, props : "altKey attrChange attrName bubbles button cancelable charCode clientX " + "clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode " + "metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX " + "screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which" .split(" "), //对事件进行包裹。 fix : function(event) { if (event[expando] == true)//表明事件已经包裹过 return event; //保存原始event,同时clone一个。 var originalEvent = event; event = { originalEvent : originalEvent }; for (var i = this.props.length, prop;i;) { prop = this.props[--i]; event[prop] = originalEvent[prop]; } event[expando] = true; //加上preventDefault and stopPropagation,在clone不会运行 event.preventDefault = function() { // 在原始事件上运行 if (originalEvent.preventDefault) originalEvent.preventDefault(); originalEvent.returnValue = false; }; event.stopPropagation = function() { // 在原始事件上运行 if (originalEvent.stopPropagation) originalEvent.stopPropagation(); originalEvent.cancelBubble = true; }; // 修正 timeStamp event.timeStamp = event.timeStamp || now(); // 修正target if (!event.target) event.target = event.srcElement || document; if (event.target.nodeType == 3)//文本节点是父节点。 event.target = event.target.parentNode; // relatedTarget 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.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.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) event.which = (event.button & 1 ? 1 : (event.button & 2 ? 3 : (event.button & 4 ? 2 : 0))); return event; }, proxy : function(fn, proxy) { // 作用就是分配全局guid. proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; return proxy; }, special : { ready : { // Make sure the ready event is setup setup : bindReady, teardown : function() { } } } }; if (!jQuery.browser.msie) { // Checks if an event happened on an element within another element // Used in jQuery.event.special.mouseenter and mouseleave handlers var withinElement = function(event) { // Check if mouse(over|out) are still within the same parent element var parent = event.relatedTarget; // Traverse up the tree while (parent && parent != this) try { parent = parent.parentNode; } catch (e) { parent = this; } if (parent != this) { // set the correct event type event.type = event.data; // handle event if we actually just moused on to a non sub-element jQuery.event.handle.apply(this, arguments); } }; jQuery.each( { mouseover : 'mouseenter', mouseout : 'mouseleave' }, function(orig, fix) { jQuery.event.special[fix] = { setup : function() { jQuery.event.add(this, orig, withinElement, fix); }, teardown : function() { jQuery.event.remove(this, orig, withinElement); } }; }); } jQuery.fn.extend( { bind : function(type, data, fn) { return type == "unload" ? this.one(type, data, fn) : this .each(function() {// fn || data, fn && data实现了data参数可有可无 jQuery.event.add(this, type, fn || data, fn && data); }); }, // 为每一个匹配元素的特定事件(像click)绑定一个一次性的事件处理函数。 // 在每个对象上,这个事件处理函数只会被执行一次。其他规则与bind()函数相同。 // 这个事件处理函数会接收到一个事件对象,可以通过它来阻止(浏览器)默认的行为。 // 如果既想取消默认的行为,又想阻止事件起泡,这个事件处理函数必须返回false。 one : function(type, data, fn) { var one = jQuery.event.proxy(fn || data, function(event) { jQuery(this).unbind(event, one); return (fn || data).apply(this, arguments);// this-->当前的元素 }); return this.each(function() { jQuery.event.add(this, type, one, fn && data); }); }, // bind()的反向操作,从每一个匹配的元素中删除绑定的事件。 // 如果没有参数,则删除所有绑定的事件。 // 你可以将你用bind()注册的自定义事件取消绑定。 // I如果提供了事件类型作为参数,则只删除该类型的绑定事件。 // 如果把在绑定时传递的处理函数作为第二个参数,则只有这个特定的事件处理函数会被删除。 unbind : function(type, fn) { return this.each(function() { jQuery.event.remove(this, type, fn); }); }, trigger : function(type, data, fn) { return this.each(function() { jQuery.event.trigger(type, data, this, true, fn); }); }, //这个特别的方法将会触发指定的事件类型上所有绑定的处理函数。但不会执行浏览器默认动作. triggerHandler : function(type, data, fn) { return this[0] && jQuery.event.trigger(type, data, this[0], false, fn); }, //每次点击后依次调用函数。 toggle : function(fn) { var args = arguments, i = 1; while (i < args.length)//每个函数分配GUID jQuery.event.proxy(fn, args[i++]); return this.click(jQuery.event .proxy(fn, function(event) {//分配GUID this.lastToggle = (this.lastToggle || 0) % i;//上一个函数 event.preventDefault();//阻止缺省动作 //执行参数中的第几个函数,apply可以采用array-like的参数 //With apply, you can use an array literal, //for example, fun.apply(this, [name, value]), //or an Array object, for example, fun.apply(this, new Array(name, value)). return args[this.lastToggle++].apply(this, arguments) || false; })); }, //一个模仿悬停事件(鼠标移动到一个对象上面及移出这个对象)的方法。 //这是一个自定义的方法,它为频繁使用的任务提供了一种“保持在其中”的状态。 //当鼠标移动到一个匹配的元素上面时,会触发指定的第一个函数。当鼠标移出这个元素时, //会触发指定的第二个函数。而且,会伴随着对鼠标是否仍然处在特定元素中的检测(例如,处在div中的图像), //如果是,则会继续保持“悬停”状态,而不触发移出事件(修正了使用mouseout事件的一个常见错误)。 hover : function(fnOver, fnOut) { return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); }, //dom ready时执行 fn ready : function(fn) { bindReady();//注册监听 if (jQuery.isReady)//ready就运行 fn.call(document, jQuery); else // 增加这个函数到queue中。可见支持无数的ready的调用。 jQuery.readyList.push(function() { return fn.call(this, jQuery); }); return this; } }); jQuery.extend( { isReady : false, readyList : [], // Handle when the DOM is ready ready : function() { if (!jQuery.isReady) { jQuery.isReady = true; if (jQuery.readyList) { jQuery.each(jQuery.readyList, function() { this.call(document); }); jQuery.readyList = null; } jQuery(document).triggerHandler("ready"); } } }); var readyBound = false; function bindReady() { if (readyBound) return; readyBound = true; // Mozilla, Opera, webkit nightlies 支持DOMContentLoaded事件 if (document.addEventListener && !jQuery.browser.opera) //当DOMContentLoaded事件触发时就运行jQuery.ready document.addEventListener("DOMContentLoaded", jQuery.ready, false); //IE或不是frame的window if (jQuery.browser.msie && window == top) (function() { if (jQuery.isReady) return; try { // 在ondocumentready之前,一直都会抛出异常 // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch (error) { //一直运行bindReady()(=arguments.callee) setTimeout(arguments.callee, 0); return; } jQuery.ready();//documentready就运行jQuery.ready })(); if (jQuery.browser.opera) document.addEventListener("DOMContentLoaded", function() { if (jQuery.isReady) return; //只有styleSheets完全enable时,才是完全的load,其实还有pic for (var i = 0;i < document.styleSheets.length; i++) if (document.styleSheets[i].disabled) {//通过styleSheets来判断 setTimeout(arguments.callee, 0); return; } jQuery.ready(); }, false); if (jQuery.browser.safari) { var numStyles; (function() { if (jQuery.isReady) return; //首先得得到readyState=loaded或=complete if (document.readyState != "loaded" && document.readyState != "complete") { setTimeout(arguments.callee, 0); return; } //取得style的length,比较它们之间的长度,看看是不是完成loaded if (numStyles === undefined) numStyles = jQuery("style, link[rel=stylesheet]").length; if (document.styleSheets.length != numStyles) { setTimeout(arguments.callee, 0); return; } jQuery.ready(); })(); } //最后只能依赖于window.load. jQuery.event.add(window, "load", jQuery.ready); } //为jquery对象增加常用的事件方法 jQuery .each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + "submit,keydown,keypress,keyup,error") .split(","), function(i, name) { jQuery.fn[name] = function(fn) { return fn ? this.bind(name, fn) : this.trigger(name); }; }); // Prevent memory leaks in IE // And prevent errors on refresh with events like mouseover in other browsers // Window isn't included so as not to unbind existing unload events jQuery(window).bind('unload', function() { for (var id in jQuery.cache) // Skip the window if (id != 1 && jQuery.cache[id].handle) jQuery.event.remove(jQuery.cache[id].handle.elem); });