Skip to content
RubyLouvre edited this page May 21, 2012 · 6 revisions

命名空间是必需的,用于精致地移除多个事件

//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 &lt; 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 &gt; 1) {
            var name = parts[1];

            for(var i = 0; i &lt; 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 &gt;= 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 &lt; 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 &lt; 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 &lt; 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 );
        }
    });

});
Clone this wiki locally