event.js
//========================================== // 事件模块(包括伪事件对象,事件绑定与事件代理) //========================================== $.define("event",document.dispatchEvent ? "node" : "node,event_fix",function(){ // $.log("已加载target模块") var rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, rmapper = /(\w+)_(\w+)/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, revent = /(^|_|:)([a-z])/g function addCallback(queue, obj){//添加回调包到列队中 var check = true, fn = obj.callback; for ( var i = 0, el; el = queue[i++]; ) { if( el.callback === fn ){ check = false; break; } } if( check ){ queue.push(obj); } } function quickIs( elem, m ) { var attrs = elem.attributes || {}; return ( (!m[1] || elem.nodeName.toLowerCase() === m[1]) && (!m[2] || (attrs.id || {}).value === m[2]) && (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) ); } //如果不存在添加一个 var facade = $.event = $.event || {}; //添加或增强二级属性eventAdapter $.Object.merge(facade,{ eventAdapter:{ focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function(src, _, _, fn ) { // We only want to do this special case on windows if ( $.type(src, "Window") ) { src.onbeforeunload = fn; } }, teardown: function( src, _, _, fn ) { if ( src.onbeforeunload === fn ) { src.onbeforeunload = null; } } } } }); var eventAdapter = $.event.eventAdapter; $.mix(facade,{ bind : function( hash ){ //它将在原生事件派发器或任何能成为事件派发器的普通JS对象添加一个名叫uniqueNumber的属性,用于关联一个缓存体, //把需要的数据储存到里面,而现在我们就把一个叫events的对象储放都它里面, //而这个event的表将用来放置各种事件类型与对应的回调函数 if(arguments.length > 1 ){ throw "$.event bind method only need one argument, and it's a hash!" } var target = this, DOM = $[ "@target" ] in target, events = $._data( target), types = hash.type, fn = hash.callback,selector = hash.selector, callback; if(target.nodeType === 3 || target.nodeType === 8 || !events){ return } hash.uuid = $.getUid(fn); //确保UUID,bag与callback的UUID一致 if( DOM ){ //处理DOM事件 callback = events.callback || (events.callback = function( e ) { return ((e || event).type !== facade.fireType ) ? facade.dispatch.apply( callback.target, arguments ) : void 0; }); callback.target = target; types = types.replace( rhoverHack, "mouseover$1 mouseout$1" ) } events = events.events || (events.events = {}); //对多个事件进行绑定 types.replace( $.rword, function( old ){ var tns = rtypenamespace.exec( old ) || [],//"focusin.aaa.bbb" namespace = ( tns[2] || "" ).split( "." ).sort(),//取得命名空间 "aaa.bbb" adapter = DOM && eventAdapter[ tns[1] ] || {},// focusin -> focus type = (selector ? adapter.delegateType : adapter.bindType ) || tns[1],//focus queue = events[ type ] = events[ type ] || [], //创建事件队列 item = $.mix({ type: type, origType: tns[1], namespace: namespace.join(".") }, hash, false); //只有原生事件发送器才能进行DOM level2 多投事件绑定 if( DOM && !queue.length ){ adapter = eventAdapter[ type ] || {}; if (!adapter.setup || adapter.setup( target, selector, item.origType, callback ) === false ) { // 为此元素这种事件类型绑定一个全局的回调,用户的回调则在此回调中执行 $.bind(target, type, callback, !!selector) } } addCallback( queue, item );//同一事件不能绑定重复回调 }); return this; }, //外部的API已经确保typesr至少为空字符串 unbind: function( hash, mappedTypes ) { var target = this, events = $._data( target, "events"); if(!events ) return; var types = hash.type || "", selector = hash.selector, fn = hash.callback, tns, type, origType, namespace, origCount, DOM = $["@target"] in target, j, adapter, queue, item; //将types进行映射并转换为数组 types = DOM ? types.replace( rhoverHack, "mouseover$1 mouseout$1" ) : types; types = types.match( $.rword ) || []; for (var t = 0; t < types.length; t++ ) { //"aaa.bbb.ccc" -> ["aaa.bbb.ccc", "aaa", "bbb.ccc"] tns = rtypenamespace.exec( types[t] ) || [] origType = type = tns[1]; namespace = tns[2]; // 如果types只包含命名空间,则去掉所有拥有此命名空间的事件类型的回调 if ( !type ) { for ( j in events ) { facade.unbind.call( target, { type: j + types[t],//说明这个types[t]为命名空间 selector: selector, callback: fn }, true ); } continue; } //如果使用事件冒充则找到其正确事件类型 adapter = eventAdapter[ type ] || {}; type = ( selector ? adapter.delegateType: adapter.bindType ) || type; queue = events[ type ] || []; origCount = queue.length; namespace = namespace ? new RegExp("(^|\\.)" + namespace.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // namespace = namespace? namespace.split( "." ).sort().join(".") : null; //只有指定了命名空间,回调或选择器才能进入此分支 if ( fn || namespace || selector ) { for ( j = 0; j < queue.length; j++ ) { item = queue[ j ]; if ( ( mappedTypes || origType === item.origType ) && ( !fn || fn.uuid === item.uuid ) &&//如果指定了回调,只检测其UUID ( !namespace || namespace.test( item.namespace ) ) &&//如果指定了命名空间 ( !selector || selector === item.selector || selector === "**" && item.selector ) ) { queue.splice( j--, 1 ); } } } else { //移除此类事件的所有回调 queue.length = 0; } if ( DOM && (queue.length === 0 && origCount !== queue.length) ) {//如果在回调队列的长度发生变化时才进行此分支 if ( !adapter.teardown || adapter.teardown( target, selector, origType, fn ) === false ) { $.unbind( target, type, $._data( target, "callback") ); } delete events[ type ]; } } if( $.isEmptyObject( events ) ){ fn = $.removeData( target,"callback") ; fn.target = null; $.removeData( target, "events") ; } return this; }, fire: function( event ){//event的类型可能是字符串,原生事件对象,伪事件对象 var target = this, namespace = [], type = event.type || event; if(!isFinite(event.mass)){ event = new jEvent(event); if( ~type.indexOf( "." ) ) {//处理命名空间 namespace = type.split("."); type = namespace.shift(); namespace.sort(); event.namespace = namespace.join( "." ); event.namespace_re = event.namespace ? new RegExp("(^|\\.)" + namespace.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; } event.target = target; } var args = [ event ].concat( $.slice(arguments,1) ); if( $["@target"] in target){ var cur = target, ontype = "on" + type; do{//模拟事件冒泡与执行内联事件 if( ($._data(cur,"events")|| {})[type] ){ facade.dispatch.apply( cur, args ); } if (cur[ ontype ] && cur[ ontype ].call(cur) === false) { event.preventDefault(); } cur = cur.parentNode || cur.ownerDocument || cur === target.ownerDocument && window; } while ( cur && !event.isPropagationStopped ); if ( !event.isDefaultPrevented ) {//模拟默认行为 click() submit() reset() focus() blur() var old;//在opera 中节点与window都有document属性 if (ontype && target[ type ] && ((type !== "focus" && type !== "blur") || target.offsetWidth !== 0) && !target.eval) { old = target[ ontype ]; if (old) { // 不用再触发内联事件 target[ ontype ] = null; } facade.fireType = type; target[ type ](); } delete facade.fireType ; if ( old ) { target[ ontype ] = old; } } }else{//普通对象的自定义事件 facade.dispatch.apply(target, args); } return this; }, filter: function( cur, parent, expr ){ var matcher = typeof expr === "function"? expr : expr.input ? quickIs : $.match for ( ; cur != parent; cur = cur.parentNode || parent ) { if(matcher(cur, expr)) return true } return false; }, dispatch: function( e ) { var win = ( this.ownerDocument || this.document || this ).parentWindow || window, event = facade.fix( e || win.event ), queue = $._data(this,"events");//这个其实是对象events if ( queue ) { queue = queue[ event.type] || [];//到此处时才是数组 event.currentTarget = this; var src = event.target,args = [event].concat($.slice(arguments,1)), result; //复制数组以防影响下一次的操作 queue = queue.concat(); //开始进行拆包操作 for ( var i = 0, item; item = queue[i++]; ) { //如果是事件代理,确保元素处于enabled状态,并且满足过滤条件 if ( !src.disabled && !(event.button && event.type === "click") && (!item.selector || facade.filter(src, this, item.selector)) && (!event.namespace || event.namespace_re.test( item.namespace ) ) ) { //取得回调函数 event.type = item.origType; result = item.callback.apply( item.selector ? src : this, args ); item.times--; if(item.times === 0){ facade.unbind.call( this, item) } if ( result !== void 0 ) { event.result = result; if ( result === false ) { event.preventDefault(); event.stopPropagation(); } } if ( event.isImmediatePropagationStopped ) { break; } } } } return event.result; }, _dispatch: function( src, type, e ){ e = facade.fix( e ); e.type = type; for(var i in src){ if(src.hasOwnProperty(i)){ facade.dispatch.call( src[ i ], e ); } } }, fix: function( event ){ if( !isFinite(event.mass) ){ var originalEvent = event event = new jEvent(originalEvent); for( var prop in originalEvent ){ //去掉所有方法与常量 if( typeof originalEvent[prop] !== "function" && prop !== "type" ){ if(/^[A-Z_]+$/.test(prop)) continue event[prop] = originalEvent[prop] } } //如果不存在target属性,为它添加一个 if ( !event.target ) { event.target = event.srcElement || document; } //safari的事件源对象可能为文本节点,应代入其父节点 if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // 处理鼠标事件 if( /^(?:mouse|contextmenu)|click/.test(event.type) ){ //如果不存在pageX/Y则结合clientX/Y做一双出来 if ( event.pageX == null && event.clientX != null ) { var doc = event.target.ownerDocument || document, html = doc.documentElement, body = doc.body; event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html && html.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html && html.clientTop || body && body.clientTop || 0); } //如果不存在relatedTarget属性,为它添加一个 if ( !event.relatedTarget && event.fromElement ) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } //标准浏览判定按下鼠标哪个键,左1中2右3 var button = event.button //IE event.button的意义 //0:没有键被按下 1:按下左键 2:按下右键 3:左键与右键同时被按下 4:按下中键 5:左键与中键同时被按下 6:中键与右键同时被按下 7:三个键同时被按下 if ( !event.which && isFinite(button) ) { event.which = [0,1,3,0,2,0,0,0][button];//0现在代表没有意义 } } if ( event.which == null ) {//处理键盘事件 event.which = event.charCode != null ? event.charCode : event.keyCode; } if( window.Touch && event.touches && event.touches[0] ){ $.log("fix touch pageXY") event.pageX = event.touches[0].pageX event.pageY = event.touches[0].pageY } //处理滚轮事件 if( event.type === "mousewheel" ){ if ("wheelDelta" in originalEvent){ var delta = originalEvent.wheelDelta/120; //opera 9x系列的滚动方向与IE保持一致,10后修正 if(top.opera && opera.version() < 10) delta = -delta; event.wheelDelta = Math.round(delta); //修正safari的浮点 bug }else if("detail" in originalEvent){ event.wheelDelta = -event.detail/3; } } // 处理组合键 if ( event.metaKey === void 0 ) { event.metaKey = event.ctrlKey; } } return event; } }); var jEvent = $.Event = function ( event ) { this.originalEvent = event.type ? event: {}; this.type = (event.type || event).replace(/\..*/g,""); this.timeStamp = Date.now(); this.mass = $.mass;//用于判定是否为伪事件对象 }; // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jEvent.prototype = { constructor: jEvent, //http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/events.html#Conformance toString: function(){ return "[object Event]" }, preventDefault: function() { this.isDefaultPrevented = true; var e = this.originalEvent; // 如果存在preventDefault 那么就调用它 if ( e.preventDefault ) { e.preventDefault(); } // 如果存在returnValue 那么就将它设为false e.returnValue = false; return this; }, stopPropagation: function() { this.isPropagationStopped = true; var e = this.originalEvent; // 如果存在preventDefault 那么就调用它 if ( e.stopPropagation ) { e.stopPropagation(); } // 如果存在returnValue 那么就将它设为true e.cancelBubble = true; return this; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = true; this.stopPropagation(); return this; } }; var types = "contextmenu,click,dblclick,mouseout,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,mousewheel," + "abort,error,load,unload,resize,scroll,change,input,select,reset,submit,input,"+"blur,focus,focusin,focusout,"+"keypress,keydown,keyup" //事件派发器的接口 //实现了这些接口的对象将具有注册事件和广播事件的功能 //http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget $.EventTarget = { uniqueNumber : $.getUid({}), defineEvents : function( names ){ var events = []; if(typeof names == "string"){ events = names.match( $.rword ) || []; }else if($.isArray(names)){ events = names; } events.forEach(function(name){ var method = 'on'+name.replace(revent,function($, $1, $2) { return $2.toUpperCase(); }); if (!(method in this)) { this[method] = function() { return $.fn.on.apply(this, [].concat.apply([name], arguments)); }; } },this); } }; "bind_on,unbind_off,fire_fire".replace( rmapper,function(_, type, mapper){ $.EventTarget[ type ] = function(){ $.fn[ mapper ].apply(this, arguments); return this; } }); $.eventSupport = function( eventName,el ) { el = el || document.createElement("div"); eventName = "on" + eventName; var ret = eventName in el; if ( el.setAttribute && !ret ) { el.setAttribute( eventName, "" ); ret = typeof el[ eventName ] === "function"; el.removeAttribute(eventName); } el = null; return ret; }; /** 用于在标准浏览器下模拟mouseenter与mouseleave 现在除了IE系列支持mouseenter/mouseleave/focusin/focusout外 opera11,FF10也支持这四个事件,同时它们也成为w3c DOM3 Event的规范 详见http://www.filehippo.com/pl/download_opera/changelog/9476/ http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html */ if( !+"\v1" || !$.eventSupport("mouseenter")){ "mouseenter_mouseover,mouseleave_mouseout".replace(rmapper, function(_, type, mapper){ eventAdapter[ type ] = { setup: function( src ){//使用事件冒充 $._data( src, type+"_handle", $.bind( src, mapper, function( e ){ var parent = e.relatedTarget; try { while ( parent && parent !== src ) { parent = parent.parentNode; } if ( parent !== src ) { facade._dispatch( [ src ], type, e ); } } catch(err) { }; })); }, teardown: function(){ $.unbind( this, mapper, $._data( type+"_handle" ) ); } }; }); } //在标准浏览器里面模拟focusin if( !$.eventSupport("focusin") ){ "focusin_focus,focusout_blur".replace(rmapper, function(_,type, mapper){ var notice = 0, focusinNotify = function (e) { var src = e.target do{//模拟冒泡 var events = $._data( src, "events" ); if(events && events[ type ]){ facade._dispatch( [ src ], type, e ); } } while (src = src.parentNode ); } eventAdapter[ type ] = { setup: function( ) { if ( notice++ === 0 ) { document.addEventListener( mapper, focusinNotify, true ); } }, teardown: function() { if ( --notice === 0 ) { document.removeEventListener( mapper, focusinNotify, true ); } } }; }); } try{ //FF需要用DOMMouseScroll事件模拟mousewheel事件 document.createEvent("MouseScrollEvents"); eventAdapter.mousewheel = { bindType : "DOMMouseScroll", delegateType: "DOMMouseScroll" } try{ //可能末来FF会支持标准的mousewheel事件,则需要删除此分支 document.createEvent("WheelEvent"); delete eventAdapter.mousewheel; }catch(e){}; }catch(e){}; //当一个元素,或者其内部任何一个元素获得焦点的时候会触发这个事件。 //这跟focus事件区别在于,他可以在父元素上检测子元素获取焦点的情况。 var rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/ function quickParse( selector ) { var quick = rquickIs.exec( selector ); if ( quick ) { // 0 1 2 3 // [ _, tag, id, class ] quick[1] = ( quick[1] || "" ).toLowerCase(); quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); } return quick; } "on_bind,off_unbind".replace( rmapper, function(_,method, mapper){ $.fn[ method ] = function(types, selector, fn ){//$.fn.on $.fn.off if ( typeof types === "object" ) { for ( var type in types ) { $.fn[ method ].call(this, type, selector, types[ type ], fn ); } return this; } var hash = {}; for(var i = 0 ; i < arguments.length; i++ ){ var el = arguments[i]; if(typeof el == "number"){ hash.times = el }else if(typeof el == "function"){ hash.callback = el }if(typeof el === "string"){ if(hash.type != null){ hash.selector = el.trim() }else{ hash.type = el.trim() } } } if(method === "on"){ if( !hash.type || !hash.callback ){//必须指定事件类型与回调 return this; } hash.times = hash.times > 0 ? hash.times : Infinity; hash.selector = hash.selector ? quickParse( hash.selector ) : false } if(this.mass && this.each){ return this.each(function() { facade[ mapper ].call( this, hash ); }); }else{ return facade[ mapper ].call( this, hash ); } } $.fn[ mapper ] = function(){// $.fn.bind $.fn.unbind return $.fn[ method ].apply(this, arguments ); } }); $.implement({ toggle: function(/*fn1,fn2,fn3*/){ var fns = [].slice.call(arguments), i = 0; return this.click(function(e){ var fn = fns[i++] || fns[i = 0, i++]; fn.call( this, e ); }) }, hover: function( fnIn, fnOut ) { return this.mouseenter( fnIn ).mouseleave( fnOut || fnIn ); }, delegate: function( selector, types, fn, times ) { return this.on( types, selector, fn, times); }, live: function( types, fn, times ) { $( this.ownerDocument ).on( types, this.selector, fn, times ); return this; }, one: function( types, fn ) { return this.on( types, fn, 1 ); }, undelegate: function(selector, types, fn ) { return arguments.length == 1? this.off( selector, "**" ) : this.off( types, fn, selector ); }, die: function( types, fn ) { $( this.ownerDocument ).off( types, fn, this.selector || "**", fn ); return this; }, fire: function( ) { var args = arguments; if(this.mass && this.each){ return this.each(function() { $.event.fire.apply(this, args ); }); }else{ return $.event.fire.apply(this, args ); } } }); types.replace( $.rword, function( type ){ $.fn[ type ] = function( callback ){ return callback? this.bind( type, callback ) : this.fire( type ); } }); });
event_fix.js
//========================================= // 事件补丁模块 //========================================== $.define("event_fix", !!document.dispatchEvent, function(){ //模拟IE678的reset,submit,change的事件代理 var rform = /^(?:textarea|input|select)$/i , changeType = { "select-one": "selectedIndex", "select-multiple": "selectedIndex", "radio": "checked", "checkbox": "checked" } function changeNotify( e ){ if( e.propertyName === ( changeType[ this.type ] || "value") ){ $._data( this, "_just_changed", true ); $.event._dispatch( $._data( this, "publisher" ), "change", e ); } } function changeFire( e ){ if( !$._data( this,"_just_changed" ) ){ $.event._dispatch( $._data( this ,"publisher"), "change", e ); }else{ $.removeData( this, "_just_changed", true ); } } function delegate( fn ){ return function( src, selector, type ){ var adapter = $.event.eventAdapter, fix = adapter[ type ] && adapter[ type ].check && adapter[ type ].check( src ); return (fix || selector) ? fn(src, type, fix) : false; } } var facade = $.event = { eventAdapter:{ //input事件的支持情况:IE9+,chrome+, gecko2+, opera10+,safari+ input: { check: function(src){ return rform.test(src.tagName) && !/^select/.test(src.type); }, bindType: "change", delegateType: "change" }, change: {//change事件的冒泡情况 IE6-9全灭 check: function(src){ return rform.test(src.tagName) && /radio|checkbox/.test(src.type) }, setup: delegate(function( src, type, fix ){ var subscriber = $._data( src, "subscriber", {} );//用于保存订阅者的UUID $._data( src, "_beforeactivate", $.bind( src, "beforeactivate", function() { var e = src.document.parentWindow.event, target = e.srcElement, tid = $.getUid( target ) //如果发现孩子是表单元素并且没有注册propertychange事件,则为其注册一个,那么它们在变化时就会发过来通知顶层元素 if ( rform.test( target.tagName) && !subscriber[ tid ] ) { subscriber[ tid] = target;//表明其已注册 var publisher = $._data( target,"publisher") || $._data( target,"publisher",{} ); publisher[ $.getUid(src) ] = src;//此孩子可能同时要向N个顶层元素报告变化 $.fn.on.call( target,"propertychange._change", changeNotify ); //允许change事件可以通过fireEvent("onchange")触发 if(type === "change"){ $._data(src, "_change_fire", $.bind(target, "change", changeFire.bind(target, e) )); } } })); if( fix ){//如果是事件绑定 src.fireEvent("onbeforeactivate") } }), teardown: delegate(function( src, els, i ){ $.unbind( src, "beforeactive", $._data( src, "_beforeactivate") ); $.unbind( src, "change", $._data(src, "_change_fire") ); els = $.removeData( src, "subscriber", true ) || {}; for( i in els){ facade.unbind.call( els[i], "._change" ); var publisher = $._data( els[i], "publisher"); if(publisher){ delete publisher[ src.uniqueNumber ]; } } }) } } } var adapter = facade.eventAdapter; //submit事件的冒泡情况----IE6-9 :form ;FF: document; chrome: window;safari:window;opera:window //reset事件的冒泡情况----FF与opera能冒泡到document,其他浏览器只能到form "submit,reset".replace( $.rword, function( type ){ adapter[ type ] = { setup: delegate(function( src ){ $.fn.on.call( src, "click._"+type+" keypress._"+type, function( e ) { var el = e.target; if( el.form && (adapter[ type ].keyCode[ e.which] || adapter[ type ].kind[ el.type ] ) ){ facade._dispatch( [ src ], type, e ); } }); }), keyCode: $.oneObject(type == "submit" ? "13,108" : "27"), kind: $.oneObject(type == "submit" ? "submit,image" : "reset"), teardown: delegate(function( src ){ facade.unbind.call( src, "._"+type ); }) }; }); }); //2012.5.1 fix delegate BUG将submit与reset这两个适配器合而为一