perf: optimization & simplification

This commit is contained in:
cha0s 2019-05-01 20:03:26 -05:00
parent ad7b03ee35
commit 353d1aec5d

View File

@ -1,6 +1,6 @@
function createListener(fn, that, type, namespace, once) { function createListener(fn, that, type, once) {
return { return {
fn, that, type, namespace, once, fn, that, type, once,
bound: that ? fn.bind(that) : fn, bound: that ? fn.bind(that) : fn,
} }
} }
@ -11,73 +11,46 @@ export function EventEmitterMixin(Superclass) {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.$$events = {}; this.$$cachedListenerLookup = () => {}
this.$$namespaces = {}; this.$$events = Object.create(null);
} }
addListener(...args) { return this.on(...args); } addListener(...args) { return this.on(...args); }
// Notify ALL the listeners! // Notify ALL the listeners!
emit(...args) { emit(type, ...args) {
const type = args[0]; const typeListeners = this.$$events[type];
const listeners = this.lookupEmitListeners(type); if (typeListeners && typeListeners.length > 0) {
if (0 === listeners.length) { this.emitToListeners(typeListeners, args);
return;
} }
}
emitToListeners(listeners, args) {
for (let i = 0; i < listeners.length; ++i) { for (let i = 0; i < listeners.length; ++i) {
const {once, type, namespace, fn, bound, that} = listeners[i]; const {once, type, fn, bound, that} = listeners[i];
const offset = type !== '*' ? 1 : 0; // Remove if only once.
if (once) { if (once) {
this.removeListener(`${type}.${namespace}`, fn); this.offSingleEvent(type, fn);
} }
// Fast path... // Fast path...
if (args.length === offset) { if (0 === args.length) {
bound() bound()
} }
else if (1 === args.length) {
else if (args.length === offset + 1) { bound(args[0]);
bound(
args[offset + 0]
);
} }
else if (2 === args.length) {
else if (args.length === offset + 2) { bound(args[0], args[1]);
bound(
args[offset + 0],
args[offset + 1]
)
} }
else if (3 === args.length) {
else if (args.length === offset + 3) { bound(args[0], args[1], args[2]);
bound(
args[offset + 0],
args[offset + 1],
args[offset + 2]
)
} }
else if (4 === args.length) {
else if (args.length === offset + 4) { bound(args[0], args[1], args[2], args[3]);
bound(
args[offset + 0],
args[offset + 1],
args[offset + 2],
args[offset + 3]
)
} }
else if (5 === args.length) {
else if (args.length === offset + 5) { bound(args[0], args[1], args[2], args[3], args[4]);
bound(
args[offset + 0],
args[offset + 1],
args[offset + 2],
args[offset + 3],
args[offset + 4]
)
} }
// Slow path... // Slow path...
else { else {
fn.apply(that, args.slice(offset)); fn.apply(that, args.slice(offset));
@ -85,101 +58,48 @@ export function EventEmitterMixin(Superclass) {
} }
} }
lookupEmitListeners(type) {
return ['*', type].reduce((r, type) => {
return r.concat(type in this.$$events ? this.$$events[type] : []);
}, []);
}
off(typesOrType, fn) { off(typesOrType, fn) {
parseTypes(typesOrType).forEach((typeOrCompositeType) => { typesOrType = Array.isArray(typesOrType) ? typesOrType : [typesOrType];
this.offSingleEvent(typeOrCompositeType, fn); for (let i = 0; i < typesOrType.length; i++) {
}); const type = typesOrType[i];
this.offSingleEvent(type, fn);
}
return this; return this;
} }
offSingleEvent(typeOrCompositeType, fn) { offSingleEvent(type, fn) {
const [type, namespace] = decomposeType(typeOrCompositeType); if ('function' !== typeof fn) {
// Only type.
// Function.
if ('function' === typeof fn) {
const lists = [];
if ((type in this.$$events)) {
lists.push(this.$$events);
}
if (
(namespace in this.$$namespaces)
&& (type in this.$$namespaces[namespace])
) {
lists.push(this.$$namespaces[namespace]);
}
lists.forEach((listeners) => {
listeners[type] = listeners[type].filter((listener) => {
return listener.fn !== fn;
});
if (0 === listeners[type].length) {
delete listeners[type];
}
});
if (
(namespace in this.$$namespaces)
) {
if (0 === Object.keys(this.$$namespaces[namespace]).length) {
delete this.$$namespaces[namespace];
}
}
return;
}
// Only type.
if (0 === namespace.length) {
if (type in this.$$events) { if (type in this.$$events) {
delete this.$$events[type]; delete this.$$events[type];
} }
for (const namespace in this.$$namespaces) {
const namespaceEvents = this.$$namespaces[namespace];
if (type in namespaceEvents) {
delete namespaceEvents[type];
}
}
return; return;
} }
// Function.
// Only namespace. const lists = [];
if (!(namespace in this.$$namespaces)) { if ((type in this.$$events)) {
return; lists.push(this.$$events);
} }
if (0 === type.length) { const listeners = this.$$events;
for (const type in this.$$namespaces[namespace]) { listeners[type] = listeners[type].filter((listener) => {
this.removeEventListenersFor(type, namespace); return listener.fn !== fn;
} });
delete this.$$namespaces[namespace]; if (0 === listeners[type].length) {
} delete listeners[type];
// Type & namespace.
else if (type in this.$$namespaces[namespace]) {
this.removeEventListenersFor(type, namespace);
delete this.$$namespaces[namespace][type];
} }
} }
on(types, fn, that = undefined) { on(typesOrType, fn, that = undefined) {
this._on(types, fn, that, false); this._on(typesOrType, fn, that, false);
return this; return this;
} }
_on(typesOrType, fn, that, once) { _on(typesOrType, fn, that, once) {
parseTypes(typesOrType).forEach((typeOrCompositeType) => { typesOrType = Array.isArray(typesOrType) ? typesOrType : [typesOrType];
this.onSingleEvent(typeOrCompositeType, fn, that, once); for (let i = 0; i < typesOrType.length; i++) {
}); const type = typesOrType[i];
this.onSingleEvent(type, fn, that, once);
}
} }
once(types, fn, that = undefined) { once(types, fn, that = undefined) {
@ -187,66 +107,17 @@ export function EventEmitterMixin(Superclass) {
return this; return this;
} }
onSingleEvent(typeOrCompositeType, fn, that, once) { onSingleEvent(type, fn, that, once) {
const [type, namespace] = decomposeType(typeOrCompositeType); const listener = createListener(fn, that, type, once);
const listener = createListener(fn, that, type, namespace, once);
if (!(type in this.$$events)) { if (!(type in this.$$events)) {
this.$$events[type] = []; this.$$events[type] = [];
} }
this.$$events[type].push(listener); this.$$events[type].push(listener);
if (!(namespace in this.$$namespaces)) {
this.$$namespaces[namespace] = {};
}
if (!(type in this.$$namespaces[namespace])) {
this.$$namespaces[namespace][type] = [];
}
this.$$namespaces[namespace][type].push(listener);
} }
removeEventListenersFor(type, namespace) { removeListener(...args) {
return this.off(...args);
for (const {fn} of this.$$namespaces[namespace][type]) {
this.$$events[type] = this.$$events[type].filter((listener) => {
return listener.fn !== fn;
});
}
if (0 === this.$$events[type].length) {
delete this.$$events[type];
}
} }
removeListener(...args) { return this.off(...args); }
} }
} }
export function decomposeType(typeOrCompositeType) {
const index = typeOrCompositeType.indexOf('.');
const isCompositeType = -1 !== index;
if (isCompositeType) {
return [
typeOrCompositeType.substr(0, index),
typeOrCompositeType.substr(index + 1),
];
}
return [typeOrCompositeType, ''];
}
// Split, flatten, and trim.
export function parseTypes(typesOrType) {
const types = Array.isArray(typesOrType) ? typesOrType : [typesOrType];
return types.map((type) => {
return type.split(' ');
}).reduce((r, types) => {
r.push(...types);
return r;
}, []).map((type) => {
return type.trim();
});
}