-
Notifications
You must be signed in to change notification settings - Fork 534
事件系统
命名空间是必需的,用于精致地移除多个事件
//KineticJS //https://github.com/ericdrowell/KineticJS/blob/master/src/Node.js on: function(typesStr, handler) { var types = typesStr.split(' '); /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ for(var n = 0; n < types.length; n++) { var type = types[n]; var event = (type.indexOf('touch') === -1) ? 'on' + type : type; var parts = event.split('.'); var baseEvent = parts[0]; var name = parts.length > 1 ? parts[1] : '';if(!this.eventListeners[baseEvent]) { this.eventListeners[baseEvent] = []; } this.eventListeners[baseEvent].push({ name: name, handler: handler }); } }, /** * remove event bindings from the node. Pass in a string of * event types delimmited by a space to remove multiple event * bindings at once such as 'mousedown mouseup mousemove'. * include a namespace to remove an event binding by name * such as 'click.foobar'. * @param {String} typesStr */ off: function(typesStr) { var types = typesStr.split(' '); for(var n = 0; n < types.length; n++) { var type = types[n]; var event = (type.indexOf('touch') === -1) ? 'on' + type : type; var parts = event.split('.'); var baseEvent = parts[0]; if(this.eventListeners[baseEvent] && parts.length > 1) { var name = parts[1]; for(var i = 0; i < this.eventListeners[baseEvent].length; i++) { if(this.eventListeners[baseEvent][i].name === name) { this.eventListeners[baseEvent].splice(i, 1); if(this.eventListeners[baseEvent].length === 0) { this.eventListeners[baseEvent] = undefined; } break; } } } else { this.eventListeners[baseEvent] = undefined; } } },
这只是一个简洁的观察者模式
//https://github.com/kangax/fabric.js/blob/master/src/observable.js fabric.Observable = { observe: function(eventName, handler) { if (!this.__eventListeners) { this.__eventListeners = { }; } // one object with key/value pairs was passed if (arguments.length === 1) { for (var prop in eventName) { this.observe(prop, eventName[prop]); } } else { if (!this.__eventListeners[eventName]) { this.__eventListeners[eventName] = [ ]; } this.__eventListeners[eventName].push(handler); } },stopObserving: function(eventName, handler) { if (!this.__eventListeners) { this.__eventListeners = { }; } if (this.__eventListeners[eventName]) { fabric.util.removeFromArray(this.__eventListeners[eventName], handler); } },
fire: function(eventName, options) { if (!this.__eventListeners) { this.__eventListeners = { } } var listenersForEvent = this.__eventListeners[eventName]; if (!listenersForEvent) return; for (var i = 0, len = listenersForEvent.length; i < len; i++) { // avoiding try/catch for perf. reasons listenersForEvent[i](options || { }); } } };
针对于context进行了强化处理
//https://github.com/documentcloud/backbone/blob/master/backbone.js var Events = Backbone.Events = {// Bind one or more space separated events, `events`, to a `callback` // function. Passing `"all"` will bind the callback to all events fired. on: function(events, callback, context) { var calls, event, list; if (!callback) return this; events = events.split(eventSplitter); calls = this._callbacks || (this._callbacks = {}); while (event = events.shift()) { list = calls[event] || (calls[event] = []); list.push(callback, context); } return this; }, // Remove one or many callbacks. If `context` is null, removes all callbacks // with that function. If `callback` is null, removes all callbacks for the // event. If `events` is null, removes all bound callbacks for all events. off: function(events, callback, context) { var event, calls, list, i; // No events, or removing *all* events. if (!(calls = this._callbacks)) return this; if (!(events || callback || context)) { delete this._callbacks; return this; } events = events ? events.split(eventSplitter) : _.keys(calls); // Loop through the callback list, splicing where appropriate. while (event = events.shift()) { if (!(list = calls[event]) || !(callback || context)) { delete calls[event]; continue; } for (i = list.length - 2; i >= 0; i -= 2) { if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) { list.splice(i, 2); } } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(events) { var event, calls, list, i, length, args, all, rest; if (!(calls = this._callbacks)) return this; rest = []; events = events.split(eventSplitter); for (i = 1, length = arguments.length; i < length; i++) { rest[i - 1] = arguments[i]; } // For each event, walk through the list of callbacks twice, first to // trigger the event, then to trigger any `"all"` callbacks. while (event = events.shift()) { // Copy callback lists to prevent modification. if (all = calls.all) all = all.slice(); if (list = calls[event]) list = list.slice(); // Execute event callbacks. if (list) { for (i = 0, length = list.length; i < length; i += 2) { list[i].apply(list[i + 1] || this, rest); } } // Execute "all" callbacks. if (all) { args = [event].concat(rest); for (i = 0, length = all.length; i < length; i += 2) { all[i].apply(all[i + 1] || this, args); } } } return this; }
};
不得不说它很优秀,试图简化jquery复杂的事件系统
//https://github.com/madrobby/zepto/blob/master/src/event.js ;(function($){ var $$ = $.zepto.qsa, handlers = {}, _zid = 1, specialEvents={}specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
function zid(element) { return element._zid || (element._zid = _zid++) } function findHandlers(element, event, fn, selector) { event = parse(event) if (event.ns) var matcher = matcherFor(event.ns) return (handlers[zid(element)] || []).filter(function(handler) { return handler && (!event.e || handler.e == event.e) && (!event.ns || matcher.test(handler.ns)) && (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector) }) } function parse(event) { var parts = ('' + event).split('.') return {e: parts[0], ns: parts.slice(1).sort().join(' ')} } function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') }
function eachEvent(events, fn, iterator){ if ($.isObject(events)) $.each(events, iterator) else events.split(/\s/).forEach(function(type){ iterator(type, fn) }) }
function add(element, events, fn, selector, getDelegate, capture){ capture = !!capture var id = zid(element), set = (handlers[id] || (handlers[id] = [])) eachEvent(events, fn, function(event, fn){ var delegate = getDelegate && getDelegate(fn, event), callback = delegate || fn var proxyfn = function (event) { var result = callback.apply(element, [event].concat(event.data)) if (result === false) event.preventDefault() return result } var handler = $.extend(parse(event), {fn: fn, proxy: proxyfn, sel: selector, del: delegate, i: set.length}) set.push(handler) element.addEventListener(handler.e, proxyfn, capture) }) } function remove(element, events, fn, selector){ var id = zid(element) eachEvent(events || '', fn, function(event, fn){ findHandlers(element, event, fn, selector).forEach(function(handler){ delete handlers[id][handler.i] element.removeEventListener(handler.e, handler.proxy, false) }) }) }
$.event = { add: add, remove: remove }
$.proxy = function(fn, context) { if ($.isFunction(fn)) { var proxyFn = function(){ return fn.apply(context, arguments) } proxyFn._zid = zid(fn) return proxyFn } else if (typeof context == 'string') { return $.proxy(fn[context], fn) } else { throw new TypeError("expected function") } }
$.fn.bind = function(event, callback){ return this.each(function(){ add(this, event, callback) }) } $.fn.unbind = function(event, callback){ return this.each(function(){ remove(this, event, callback) }) } $.fn.one = function(event, callback){ return this.each(function(i, element){ add(this, event, callback, null, function(fn, type){ return function(){ var result = fn.apply(element, arguments) remove(element, type, fn) return result } }) }) }
var returnTrue = function(){return true}, returnFalse = function(){return false}, eventMethods = { preventDefault: 'isDefaultPrevented', stopImmediatePropagation: 'isImmediatePropagationStopped', stopPropagation: 'isPropagationStopped' } function createProxy(event) { var proxy = $.extend({originalEvent: event}, event) $.each(eventMethods, function(name, predicate) { proxy[name] = function(){ this[predicate] = returnTrue return event[name].apply(event, arguments) } proxy[predicate] = returnFalse }) return proxy }
// emulates the 'defaultPrevented' property for browsers that have none function fix(event) { if (!('defaultPrevented' in event)) { event.defaultPrevented = false var prevent = event.preventDefault event.preventDefault = function() { this.defaultPrevented = true prevent.call(this) } } }
$.fn.delegate = function(selector, event, callback){ var capture = false if(event == 'blur' || event == 'focus'){ if($.iswebkit) event = event == 'blur' ? 'focusout' : event == 'focus' ? 'focusin' : event else capture = true }
return this.each(function(i, element){ add(element, event, callback, selector, function(fn){ return function(e){ var evt, match = $(e.target).closest(selector, element).get(0) if (match) { evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) return fn.apply(match, [evt].concat([].slice.call(arguments, 1))) } } }, capture) })
} $.fn.undelegate = function(selector, event, callback){ return this.each(function(){ remove(this, event, callback, selector) }) }
$.fn.live = function(event, callback){ $(document.body).delegate(this.selector, event, callback) return this } $.fn.die = function(event, callback){ $(document.body).undelegate(this.selector, event, callback) return this }
$.fn.on = function(event, selector, callback){ return selector == undefined || $.isFunction(selector) ? this.bind(event, selector || callback) : this.delegate(selector, event, callback) } $.fn.off = function(event, selector, callback){ return selector == undefined || $.isFunction(selector) ? this.unbind(event, selector || callback) : this.undelegate(selector, event, callback) }
$.fn.trigger = function(event, data){ if (typeof event == 'string') event = $.Event(event) fix(event) event.data = data return this.each(function(){ // items in the collection might not be DOM elements // (todo: possibly support events on plain old objects) if('dispatchEvent' in this) this.dispatchEvent(event) }) }
// triggers event handlers on current element just as if an event occurred, // doesn't trigger an actual event, doesn't bubble $.fn.triggerHandler = function(event, data){ var e, result this.each(function(i, element){ e = createProxy(typeof event == 'string' ? $.Event(event) : event) e.data = data e.target = element $.each(findHandlers(element, event.type || event), function(i, handler){ result = handler.proxy(e) if (e.isImmediatePropagationStopped()) return false }) }) return result }
// shortcut methods for
.bind(event, fn)
for each event type ;('focusin focusout load resize scroll unload click dblclick '+ 'mousedown mouseup mousemove mouseover mouseout '+ 'change select keydown keypress keyup error').split(' ').forEach(function(event) { $.fn[event] = function(callback){ return this.bind(event, callback) } });['focus', 'blur'].forEach(function(name) { $.fn[name] = function(callback) { if (callback) this.bind(name, callback) else if (this.length) try { this.get(0)name } catch(e){} return this } })
$.Event = function(type, props) { var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null) return event }
})(Zepto)
$.define("event", "node" ,function(){ $.log("已加载event2模块") var rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, rmapper = /(\w+)_(\w+)/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, revent = /(^|_|:)([a-z])/g //如果不存在添加一个 var facade = $.event = $.event || {}; $.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; }; //添加或增强二级属性eventAdapter $.Object.merge(facade,{ eventAdapter:{ focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" } } }); var eventAdapter = $.event.eventAdapter; var wrapper = function(hash){ // console.log(hash) var fn = function(event){ var src = hash.src; var ret = hash.callback.apply(src, arguments) if (ret === false) event.preventDefault() hash.times--; if(hash.times === 0){ facade.unbind.call( src, hash) } return ret; } fn.uuid = hash.uuid; return fn; } function eachEvent(events, fn, iterator){ if ($.type(events, "Object")) $.each(events, iterator) else events.split(/\s/).forEach(function(type){ iterator(type, fn) }) } function parse(event) { var parts = ('' + event).split('.') return { type: parts[0], ns: parts.slice(1).sort().join(' ') } } function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') } function findHandlers(hash, events) { var obj = parse(hash.type) var namespace = obj.ns ? matcherFor(obj.ns) : null; var fn = hash.callback; var selector = hash.selector return item && (events[ obj.type ] || []).filter(function(item) { return (!obj.type || obj.type === item.origType) && (!namespace || namespace.test(item.namespace)) && (!fn || fn.uuid === item.uuid) && (!selector || selector === item.selector || selector === "**" && item.selector ) }) } $.mix(facade,{ bind: function( hash ){ 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 if(target.nodeType === 3 || target.nodeType === 8 || !events){ return } if( DOM ){ //处理DOM事件 types = types.replace( rhoverHack, "mouseover$1 mouseout$1" ); } events = events.events || (events.events = {}); hash.uuid = $.getUid(fn); //确保UUID,bag与callback的UUID一致 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 var isCustom = !DOM || !$.eventSupport(type) var item = $.mix({ target: isCustom ? window : target,//如果是自定义事件,使用window来代理 src: target, isCustom: isCustom, type: type, origType: tns[1], namespace: namespace.join(".") }, hash, false); events[ type ] = events[ type ] || []; events[ type ].push(item); item.proxy = wrapper(item) item.target.addEventListener(type,item.proxy,!!item.selector ) }) }, //外部的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 ] || []; namespace = namespace ? new RegExp("(^|\\.)" + namespace.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // namespace = namespace? namespace.split( "." ).sort().join(".") : null; //只有指定了命名空间,回调或选择器才能进入此分支 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 ) ) { item.target.removeEventListener(item.type, item.proxy,!!item.selector ) queue.splice( j--, 1 ); } } if ( queue.length === 0 ) {//如果在回调队列的长度发生变化时才进行此分支 delete events[ type ]; } } if( $.isEmptyObject( events ) ){ $.removeData( target, "events") ; } return this; } }); "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 ); } }); });