最近对事件模块进行疯狂升级的成果。
define("event", top.dispatchEvent ? ["$node"] : ["$node","$event_fix"],function(){
$.log("已加载event模块v8")
var facade = $.event || ($.event = {});
var adapter = $.eventAdapter || ($.eventAdapter = {})
var rhoverHack = /(?:^|\s)hover(\.\S+|)\b/
var bindTop = !adapter.change;//如果没有加载event_fix模块,也就没有change分支,也就说明其是支持dispatchEvent API
$.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;
};
/**
* 从事件类型中分解出有用的信息
* @param {String} event 事件类型
* @param {String|Boolean|Undefined} live 用于判定是否使用代理
*/
var Event = function(event, live){
var parts = event.split('.');
var ns = parts.slice(1).sort().join(' ');
var type = parts[0], hack, tmp;//input -> change -> propertychange
while( (hack = adapter[ type ]) ){
tmp = hack[ live ? "delegateType" : "bindType" ];
if( tmp ){
type = tmp
}else{
break
}
}
//比如在chrome下fire mouseenter, 到这里type为mouseover, origType为mouseenter
this.type = type; //事件类型
this.origType = parts[0] //原事件类型
this.live = live; //是否使用了事件代理,可以是正则,字符串,布尔或空值
this.ns = ns, //命名空间
this.rns = ns ? new RegExp("(^|\\.)" + ns.replace(' ', ' .* ?') + "(\\.|$)") : null
}
//events为要过滤的集合,后面个参数为过滤条件
function findHandlers( events, hash, fn, live ) {
return events.filter(function(desc) {
return desc && (!hash.rns || hash.rns.test(desc.ns)) //通过事件类型进行过滤
&& (!hash.origType || hash.origType === desc.origType) //通过命名空间进行进行过滤
&& (!fn || fn.uniqueNumber === desc.uuid) //通过uuid进行过滤
&& (!live || live === desc.live || live === "**" && desc.live )//通过选择器进行过滤
})
}
Event.prototype = {
toString: function(){
return "[object Event]"
},
preventDefault: function() {
this.defaultPrevented = true;
var e = this.originalEvent || {};
if (e && e.preventDefault ) {
e.preventDefault();
}// 如果存在returnValue 那么就将它设为false
e.returnValue = false;
return this;
},
stopPropagation: function() {
var e = this.originalEvent || {};
if (e && e.stopPropagation ) {
e.stopPropagation();
}
//http://opera.im/kb/userjs/
e.cancelBubble = this.propagationStopped = true;
return this;
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = true;
this.stopPropagation();
return this;
}
}
$.Event = Event;
$.mix(facade,{
//addEventListner API的支持情况:chrome 1+ FF1.6+ IE9+ opera 7+ safari 1+;
//http://functionsource.com/post/addeventlistener-all-the-way-back-to-ie-6
bind: function( target, hash ){//事件系统三大核心方法之一,绑定事件
var bindTarget = $[ "@bind" ] in target,//是否能直接绑定到目标对象上
data = $._data( target ), //是否能绑定事件
types = hash.type, //原有的事件类型,可能是复数个
live = hash.live; //是否使用事件代理
if( !data ){
return
}
if( bindTarget ){ //处理DOM的hover事件
types = types.replace( rhoverHack, "mouseover$1 mouseout$1" );
}
var events = data.events || (data.events = []);
hash.uuid = $.getUid( hash.fn ); //确保hash.uuid与fn.uuid一致
types.replace( $.rword, function( t ){
var desc = new $.Event( t, live), type = desc.origType;
$.mix(desc, {
currentTarget: target, //this,用于绑定数据的
index: events.length //记录其在列表的位置,在卸载事件时用
}, hash, false);
events.push( desc ); //用于事件拷贝
var count = events[ type+"_count" ] = ( events[ type+"_count" ] | 0 )+ 1;
var hack = adapter[ desc.type ] || {};
if(hack.add){
hack.add(desc)
}
if( count == 1 ){
var handle = data[type+"_handle"] = facade.curry( desc ); // 一个curry
if( !hack.setup || hack.setup( desc ) === false ) {
if( bindTop && !bindTarget ){//如果不能绑到当前对象上,尝试绑到window上
target = window;
}
//此元素在此事件类型只绑定一个回调
$.bind(target, desc.type, handle);
}
}
});
},
curry: function( hash ){// 这是元信息,不要污染这个对象
var fn = function( event){//真正的事件对象
var type = hash.origType;//用户在调用API时绑定的事件
var ctarget = hash.currentTarget//原来绑定事件的对象
var more = event.more || {};
//第一个分支防止在旧式IE下,fire click时二次触发
//第二个分支防止在chrome下,fire mouseover时,把用于冒充mouseenter用的mouseover也触发了
if(facade.type == type || more.origType && more.origType !== type ){
return
}
var queue = ( $._data( ctarget, "events") || [] );
//如果是自定义事件, 或者旧式IE678, 或者需要事件冒充
if( !event.originalEvent || !bindTop || hash.type !== type ){
event = facade.fix( hash, event, type );
}
var args = [ event ].concat( event.args || [] ), result,lives = [], handlers = []
for ( var i = 0, desc; desc = queue[i++]; ) {
if(desc.live){
lives.push(desc)
}else{
handlers.push(desc)
}
}
//DOM树的每一个元素都有可以作为代理节点
if ( lives.length && !(event.button && type === "click") ) {
for ( var k = 0, cur; (cur = lives[k++]) ; ) {
var cc = cur.currentTarget
var nodes = $(cc).find(cur.live);
for(var node = event.target; node != cc; node = node.parentNode || cc ){
if ( node.disabled !== true || type !== "click" ) {
if( nodes.index(node) !== -1){
handlers.push({
elem: node,
fn: cur.fn,
origType: cur.origType,
ns: cur.ns
});
}
}
}
}
}
for ( var i = 0, desc; desc = handlers[i++]; ) {
if ( ( event.type == desc.origType ) &&
(!event.rns || event.rns.test( desc.ns )) ) {
//谁绑定了事件,谁就是事件回调中的this
if(desc.preHandle && desc.preHandle(desc.elem || ctarget, event, desc) === false){
return
}
result = desc.fn.apply( desc.elem || ctarget, args);
if(desc.postHandle && desc.postHandle(desc.elem || ctarget, event, desc) === false){
return
}
desc.times--;
if(desc.times === 0){//如果有次数限制并到用光所有次数,则移除它
facade.unbind( this, desc)
}
if ( result !== void 0 ) {
event.result = result;
if ( result === false ) {
event.preventDefault();
event.stopPropagation();//这个参照jQuery的行为办事
}
}
if ( event.propagationStopped ) {
break;
}
}
}
}
return fn;
},
dispatch: function( target, event, type ){// level2 API 用于旧式的$.event.fire中
var handle = $._data(target, (type || event.type) +"_handle" );//取得此元素此类型的第一个quark
handle && handle.call( target, event )
},
//将真事件对象的成员赋给伪事件对象,抹平浏览器差异
fix: function( event, real, type){
if( !event.originalEvent ){
var hash = event, toString = hash.toString;//IE无法遍历出toString;
event = $.Object.merge({}, hash);//这里event只是一个伪事件对象
for( var p in real ){
if( !(p in hash) ){
event[p] = real[p]
}
}
for( var p in real.more ){
if( !(p in hash) ){
event[p] = real.more[p]
}
}
event.toString = toString;
event.originalEvent = real;
event.timeStamp = Date.now();
//如果不存在target属性,为它添加一个
if ( !event.target ) {
event.target = event.srcElement || document;
}
//safari的事件源对象可能为文本节点,应代入其父节点
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
event.metaKey = !!event.ctrlKey; // 处理IE678的组合键
if( /^(?:mouse|contextmenu)|click/.test( type ) ){
if ( event.pageX == null && event.clientX != null ) { // 处理鼠标事件
var doc = event.target.ownerDocument || document;
var box = document.compatMode == "BackCompat" ? doc.body : doc.documentElement
event.pageX = event.clientX + (box && box.scrollLeft || 0) - (box && box.clientLeft || 0);
event.pageY = event.clientY + (box && box.scrollTop || 0) - (box && box.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( type === "mousewheel" ){ //处理滚轮事件
if ("wheelDelta" in real){//统一为±120,其中正数表示为向上滚动,负数表示向下滚动
// http://www.w3help.org/zh-cn/causes/SD9015
var delta = real.wheelDelta
//opera 9x系列的滚动方向与IE保持一致,10后修正
if( window.opera && opera.version() < 10 )
delta = -delta;
event.wheelDelta = Math.round(delta); //修正safari的浮点 bug
}else if( "detail" in real ){
event.wheelDelta = -real.detail * 40;//修正FF的detail 为更大众化的wheelDelta
}
}
}else if ( event.which == null ) {//处理键盘事件
event.which = event.charCode != null ? event.charCode : event.keyCode;
}else if( window.Touch && event.touches && event.touches[0] ){
event.pageX = event.touches[0].pageX//处理触摸事件
event.pageY = event.touches[0].pageY
}
}
if( type ){
event.type = type
}
return event;
},
//外部的API已经确保typesr至少为空字符串
unbind: function( target, hash ) {//事件系统三大核心方法之一,卸载事件
var events = $._data( target, "events");
if( !events ) return;
var types = hash.type || "", live = hash.live, bindTarget = $["@bind"] in target;
if( bindTarget ){ //处理DOM的hover事件
types = types.replace( rhoverHack, "mouseover$1 mouseout$1" );
}
types.replace( $.rword, function( t ){
var desc = new $.Event( t, live ), type = desc.origType, hack = adapter[ type ] || {};
findHandlers( events, desc , hash.fn, live ).forEach( function(desc){
if( --events[type+"_count"] == 0 ){
if( !hack.teardown || hack.teardown( desc ) === false ) {
if( bindTarget === false && bindTop ){//如果不能绑到当前对象上,尝试绑到window上
target = window;
}
var handle = $._data(target, type+"_handle");
$.unbind( target, desc.type, handle );
}
$.removeData( target, type +"_handle", true );
delete events[ type+"_count"];
}
events[ desc.index ] = null;
})
});
for ( var i = events.length; i >= 0; i-- ) {
if (events[i] == null){
events.splice(i, 1);
}
}
if( !events.length ){
$.removeData( target, "events") ;
}
}
})
var unbubbleEvents = $.oneObject("load unload focus blur mouseenter mouseleave",1)
if( bindTop ){//事件系统三大核心方法之一,触发事件
facade.fire = function( type ){
var bindTarget = $["@bind"] in this, more, event
var target = bindTarget ? this : window;
if(typeof type == "string"){
more = new Event( type );
}else if(type && type.preventDefault){
if(!( type instanceof $.Event) ){//如果是真的事件对象
more = new Event( type.type );
event = type;
}else{
more = type;//如果是$.Event实例
}
}
if( more ){
type = more.type;
var doc = target.ownerDocument || target.document || target || document;
if(!event){
event = doc.createEvent(eventMap[type] || "CustomEvent");
event.initEvent( type,!unbubbleEvents[type],true, doc.defaultView, 1);//, doc.defaultView
}
event.args = [].slice.call( arguments, 1 ) ;
event.more = more
target.dispatchEvent(event);
if(/^(focus|blur|select|reset)$/.test(type)){
target[type] && target[type]()
}
}else{
throw "fire的第一个参数是必须是事件类或真伪事件对象 "
}
}
}
var rmapper = /(\w+)_(\w+)/g;
//以下是用户使用的API
$.implement({
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;
return this.each(function() {
$.event.fire.apply(this, args );
});
}
});
//这个迭代器产生四个重要的事件绑定API on off bind unbind
var rtypes = /^[a-z0-9_\-\.\s\,]+$/i
"on_bind,off_unbind".replace( rmapper, function(_,method, mapper){
$.fn[ method ] = function(types, selector, fn ){
if ( typeof types === "object" ) {
for ( var type in types ) {
$.fn[ method ](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.fn = el
}
if(typeof el === "string"){
if(hash.type != null){
hash.live = el.trim();
}else{
hash.type = el.trim();//只能为字母数字-_.空格
if(!rtypes.test(hash.type)){
throw "事件类型格式不正确"
}
}
}
}
if(!hash.type){
throw "必须指明事件类型"
}
if(method === "on" && !hash.fn ){
throw "必须指明事件回调"
}
hash.times = hash.times > 0 ? hash.times : Infinity;
return this.each(function() {
facade[ mapper ]( this, hash );
});
}
$.fn[ mapper ] = function(){// $.fn.bind $.fn.unbind
return $.fn[ method ].apply(this, arguments );
}
});
var mouseEvents = "contextmenu,click,dblclick,mouseout,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,mousewheel,"
var eventMap = $.oneObject(mouseEvents, "MouseEvents");
var types = mouseEvents +",keypress,keydown,keyup," + "blur,focus,focusin,focusout,"+
"abort,error,load,unload,resize,scroll,change,input,select,reset,submit"//input
types.replace( $.rword, function( type ){//这里产生以事件名命名的快捷方法
eventMap[type] = eventMap[type] || (/key/.test(type) ? "KeyboardEvent" : "HTMLEvents")
$.fn[ type ] = function( callback ){
return callback? this.bind( type, callback ) : this.fire( type );
}
});
/**
mouseenter/mouseleave/focusin/focusout已为标准事件,经测试IE5+,opera11,FF10+都支持它们
详见http://www.filehippo.com/pl/download_opera/changelog/9476/
*/
if( !+"\v1" || !$.eventSupport("mouseenter")){//IE6789不能实现捕获与safari chrome不支持
"mouseenter_mouseover,mouseleave_mouseout".replace(rmapper, function(_, type, mapper){
adapter[ type ] = {
add: function(desc){
desc.preHandle = function(target, event){
var related = event.relatedTarget;
return !related || (related !== target && !$.contains( target, related ))
}
}
};
if(!$.eventSupport("mouseenter")){
adapter[ type ].bindType = adapter[ type ].delegateType = mapper
}
});
}
//现在只有firefox不支持focusin,focusout事件,并且它也不支持DOMFocusIn,DOMFocusOut,不能像DOMMouseScroll那样简单冒充
if( !$.support.focusin ){
"focusin_focus,focusout_blur".replace(rmapper, function(_,type, mapper){
var notice = 0, handler = function (event) {
var src = event.target;
do{//模拟冒泡
if( $._data(src, "events") ) {
event.more = event.more ||{}
event.more.type = type
facade.dispatch( src, event, type );
}
} while (src = src.parentNode );
}
adapter[ type ] = {
setup: function( ) {
if ( notice++ === 0 ) {
document.addEventListener( mapper, handler, true );
}
},
teardown: function() {
if ( --notice === 0 ) {
document.removeEventListener( mapper, handler, true );
}
}
};
});
}
try{
//FF需要用DOMMouseScroll事件模拟mousewheel事件
document.createEvent("MouseScrollEvents");
adapter.mousewheel = {
bindType : "DOMMouseScroll",
delegateType: "DOMMouseScroll"
}
if($.eventSupport("mousewheel")){
delete adapter.mousewheel;
}
}catch(e){};
})
event_fix模块,重构更多!
//=========================================
// 事件补丁模块
//==========================================
define("event_fix", !!document.dispatchEvent, function(){
$.log("已加载event_fix模块",7)
var facade = $.event = {
fire: function( init ){
//这里的代码仅用于IE678
var transfer;
if( typeof init == "string"){
transfer = new $.Event(init);
init = false;
}
if( init && typeof init == "object"){
if( init instanceof $.Event ){//如果是伪的
transfer = init;
}else if( "cancelBubble" in init){
transfer = new $.Event(init.type);
transfer.originalEvent = init
}
}
if(!transfer){
throw "fire的第一个参数是必须是事件类或真伪事件对象"
}
transfer.target = this;
transfer.args = [].slice.call(arguments,1) ;
var type = transfer.origType || transfer.type
if( $["@bind"] in this ){
var cur = this, ontype = "on" + type;
do{//模拟事件冒泡与执行内联事件
facade.dispatch( cur, transfer, type );
if (cur[ ontype ] && cur[ ontype ].call(cur) === false) {
transfer.preventDefault();
}
cur = cur.parentNode ||
cur.ownerDocument ||
cur === cur.ownerDocument && window; //在opera 中节点与window都有document属性
} while ( cur && !transfer.propagationStopped );
if ( !transfer.defaultPrevented ) {//如果用户没有阻止普通行为,defaultPrevented
if( !(type === "click" && this.nodeName === "A") ) { //并且事件源不为window,并且是原生事件
if ( ontype && this[ type ] && ((type !== "focus" && type !== "blur") || this.offsetWidth !== 0) && !this.eval ) {
var inline = this[ ontype ];
//当我们直接调用元素的click,submit,reset,focus,blur
//会触发其默认行为与内联事件,但IE下会再次触发内联事件与多投事件
this[ ontype ] = null;
facade.type = type
if(type == "click" && /checkbox|radio/.test(this.type)){
this.checked = !this.checked
}
this[ type ]();
facade.type = void 0
this[ ontype ] = inline;
}
}
}
}else{//普通对象的自定义事件
facade.dispatch(this, transfer);
}
}
}
//模拟IE678的reset,submit,change的事件代理
var rform = /^(?:textarea|input|select)$/i
function changeNotify( event ){
if( event.type == "change" || event.propertyName == "checked" ){
$.event.fire.call(this,"change")
}
}
function delegate( fn ){
return function( item ){//用于判定是否要使用代理
return item.live ? fn( item.currentTarget, item ) : false;
}
}
var adapter = $.eventAdapter = {
focus: {
delegateType: "focusin"
},
blur: {
delegateType: "focusout"
},
change: {//change事件的冒泡情况 IE6-9全灭
//详见这里https://github.com/RubyLouvre/mass-Framework/issues/13
setup: delegate(function( node, desc ){
var subscriber = desc.subscriber || ( desc.subscriber = {}) //用于保存订阅者的UUID
desc.__beforeactive__ = $.bind( node, "beforeactivate", function(event) {
var target = event.srcElement;
var tid = $.getUid( target )
//如果发现孩子是表单元素并且没有注册propertychange事件,则为其注册一个,那么它们在变化时就会发过来通知顶层元素
if ( rform.test( target.tagName) && !subscriber[ tid ] ) {
subscriber[ tid ] = target;//将select, checkbox, radio, text, textarea等表单元素注册其上
if(/checkbox|radio/.test(target.type)){
desc.__change__ = $.bind( target, "propertychange", changeNotify.bind(target, event) );
}else{
desc.__change__ = $.bind( target, "change", changeNotify.bind(target, event) );
}
}
});//如果是事件绑定
// node.fireEvent("onbeforeactivate")
}),
teardown: delegate(function( node, desc ){
$.unbind( node, "beforeactive", desc.__beforeactive__ );
var els = desc.subscriber ;
for(var i in els){
$.unbind( els[i], "propertychange", desc.__change__) ;
$.unbind( els[i], "change", desc.__change__);
}
})
}
}
//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( node ){
$(node).bind( "click._"+type+" keypress._"+type, function( event ) {
var el = event.target;
if( el.form && (adapter[ type ].keyCode[ event.which ] || adapter[ type ].input[ el.type ] ) ){
$.event.fire.call(el, type)
}
});
}),
keyCode: $.oneObject(type == "submit" ? "13,108" : "27"),
input: $.oneObject(type == "submit" ? "submit,image" : "reset"),
teardown: delegate(function( node ){
$( node ).unbind( "._"+type );
})
};
});
})