refactoring of events, it should be about 4 times faster now

This commit is contained in:
Fabian Zeindl 2014-06-21 18:26:16 +02:00 committed by Per Liedman
parent d70602eb60
commit 0210071dad
2 changed files with 108 additions and 89 deletions

View File

@ -247,17 +247,32 @@ describe('Events', function () {
obj.removeEventListener('test', spy, foo2);
expect(obj.listens('test')).to.be(false);
//Add and remove a listener without context
obj.addEventListener('test', spy);
obj.removeEventListener('test', spy);
expect(obj.listens('test')).to.be(false);
});
it('makes sure an event is not triggered if a listener is removed during dispatch', function () {
var obj = new L.Evented(),
spy = sinon.spy();
spy = sinon.spy(),
spy2 = sinon.spy(),
spy3 = sinon.spy(),
foo = {};
/* without context */
obj.addEventListener('test', function () { obj.removeEventListener('test', spy); });
obj.addEventListener('test', spy);
obj.fireEvent('test');
expect(spy.called).to.be(false);
/* with context */
obj.addEventListener('test2', function () { obj.removeEventListener('test2', spy2, foo); }, foo);
obj.addEventListener('test2', spy2, foo);
obj.fireEvent('test2');
});
it('makes sure an event is not triggered if all listeners are removed during dispatch', function () {

View File

@ -70,7 +70,7 @@ L.Evented = L.Class.extend({
if (!types) {
// clear all listeners if called without arguments
delete this._events;
this._events = undefined;
} else if (typeof types === 'object') {
for (var type in types) {
@ -90,95 +90,97 @@ L.Evented = L.Class.extend({
// attach listener (without syntactic sugar now)
_on: function (type, fn, context) {
this._events = this._events || {};
var events = this._events = this._events || {},
contextId = context && context !== this && L.stamp(context);
if (contextId) {
// store listeners with custom context in a separate hash (if it has an id);
// gives a major performance boost when firing and removing events (e.g. on map object)
var indexKey = type + '_idx',
indexLenKey = type + '_len',
typeIndex = events[indexKey] = events[indexKey] || {},
id = L.stamp(fn) + '_' + contextId;
if (!typeIndex[id]) {
typeIndex[id] = {fn: fn, ctx: context};
// keep track of the number of keys in the index to quickly check if it's empty
events[indexLenKey] = (events[indexLenKey] || 0) + 1;
/* get/init listeners for type */
var typeListeners = this._events[type];
if (!typeListeners) {
typeListeners = {
listeners: {},
count: 0
};
this._events[type] = typeListeners;
}
} else {
// individual layers mostly use "this" for context and don't fire listeners too often
// so simple array makes the memory footprint better while not degrading performance
var contextId = context && context !== this && L.stamp(context),
newListener = {fn: fn, ctx: context};
events[type] = events[type] || [];
events[type].push({fn: fn});
if (!contextId) {
contextId = 'no_context';
newListener.ctx = undefined;
}
// fn array for context
var listeners = typeListeners.listeners[contextId];
if (!listeners) {
listeners = [];
typeListeners.listeners[contextId] = listeners;
}
// check if fn already there
var found = false;
for (var i=0, len=listeners.length; i<len; i++) {
if (listeners[i].fn === fn) {
found = true;
break;
}
}
if (!found) {
listeners.push(newListener);
typeListeners.count++;
}
},
_off: function (type, fn, context) {
var events = this._events,
indexKey = type + '_idx',
indexLenKey = type + '_len',
listener, listeners, i, len;
if (!events) { return; }
if (!this._events) { return; }
if (!fn) {
// clear all listeners for a type if function isn't specified
// set the removed listeners to noop so that's not called if remove happens in fire
listeners = events[indexKey];
for (i in listeners) {
listeners[i].fn = L.Util.falseFn;
}
listeners = events[type] || [];
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = L.Util.falseFn;
}
delete events[type];
delete events[indexKey];
delete events[indexLenKey];
delete this._events[type];
return;
}
var contextId = context && context !== this && L.stamp(context),
id;
if (contextId) {
id = L.stamp(fn) + '_' + contextId;
listeners = events[indexKey];
if (listeners && listeners[id]) {
listener = listeners[id];
delete listeners[id];
events[indexLenKey]--;
var typeListeners = this._events[type];
if (!typeListeners) {
return;
}
} else {
listeners = events[type];
var contextId = context && context !== this && L.stamp(context);
if (!contextId) {
contextId = 'no_context';
}
var listeners = typeListeners.listeners[contextId];
if (listeners) {
for (i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn) {
listener = listeners[i];
listeners.splice(i, 1);
break;
// find fn and remove it
for (var i=0, len = listeners.length; i<len; i++) {
var l = listeners[i];
if (l.fn === fn) {
// set the removed listener to noop so that's not called if remove happens in fire
l.fn = L.Util.falseFn;
typeListeners.count--;
if (len > 1) {
if (!this._isFiring) {
listeners.splice(i,1);
} else {
/* copy array in case events are being fired */
typeListeners.listeners[contextId] = listeners.slice().splice(i,1);
}
} else {
delete typeListeners.listeners[contextId];
}
return;
}
if (listeners.length === 0) {
delete events[type];
}
}
}
// set the removed listener to noop so that's not called if remove happens in fire
if (listener) {
listener.fn = L.Util.falseFn;
}
},
// @method fire(type: String, data?: Object, propagate?: Boolean): this
@ -188,26 +190,29 @@ L.Evented = L.Class.extend({
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
var event = L.Util.extend({}, data, {type: type, target: this}),
events = this._events;
var event = L.Util.extend({}, data, {type: type, target: this});
if (events) {
var typeIndex = events[type + '_idx'],
i, len, listeners, id;
if (this._events) {
var typeListeners = this._events[type];
if (events[type]) {
// make sure adding/removing listeners inside other listeners won't cause infinite loop
listeners = events[type].slice();
this._isFiring = true;
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn.call(this, event);
// each context
for (var contextId in typeListeners.listeners) {
var listeners = typeListeners.listeners[contextId];
// each fn in context
for (var i=0, len = listeners.length; i<len; i++) {
var l = listeners[i];
if (l.ctx) {
l.fn.call(l.ctx, event);
} else {
l.fn.call(this, event);
}
}
}
// fire event for the context-indexed listeners as well
for (id in typeIndex) {
typeIndex[id].fn.call(typeIndex[id].ctx, event);
}
this._isFiring = false;
}
if (propagate) {
@ -221,9 +226,8 @@ L.Evented = L.Class.extend({
// @method listens(type: String): Boolean
// Returns `true` if a particular event type has any listeners attached to it.
listens: function (type, propagate) {
var events = this._events;
if (events && (events[type] || events[type + '_len'])) { return true; }
var typeListeners = this._events && this._events[type];
if (typeListeners && typeListeners.count) { return true; }
if (propagate) {
// also check parents for listeners if event propagates