jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
var splitEventString,
Observer;
splitEventString = function(str) {
return str.trim().split(/\s+/);
};
/* The Observer Constructor
================================================================================== */
Observer = function() {
this._events = {};
};
/* The Observer's prototyped methods
================================================================================== */
Observer.prototype = {
on: function(event, callback, context) {
// split the events from a space-delimited string into an array
var events = splitEventString(event),
i;
/**
* loop through the events and push the callback/context into the
* instance's _events object's appropriate array
*/
for (i = 0; i < events.length; i++) {
event = events[i];
this._events[event] = this._events[event] || [];
this._events[event].push({
callback: callback,
context: context
});
}
return this;
},
off: function(event, callback, context) {
// if there isn't an event or callback passed, remove all bound events
if (!event && !callback) {
this._events = {};
} else {
// split the events from a space-delimited string into an array
var events = splitEventString(event),
retain,
listeners,
i,
j;
// loop through the events
for (i = 0; i < events.length; i++) {
event = events[i];
listeners = this._events[event];
// if an event has bound listeners
if (listeners) {
/* create a new array of listeners to be retained */
this._events[event] = retain = [];
/*
* loop through the existing listeners and if a callback is passed in
* and the callbacks don't match or a context is passed in and that
* doesn't match, retain them
*/
for (j = 0; j < listeners.length; j++) {
if ((callback && listeners[j].callback !== callback) || (context && context !== listeners[j].context)) {
retain.push(listeners[j]);
}
}
// if no events were retained, delete the array entirely
if (!retain.length) {
delete this._events[event];
}
}
}
}
return this;
},
trigger: function(event) {
var listeners = this._events[event],
listener,
args = Array.prototype.slice.call(arguments, 1),
i;
if (listeners) {
for (i = 0; (listener = listeners[i]); i++) {
listener.callback.apply(listener.context || this, args);
}
}
return this;
},
one: function(event, callback, context) {
var self = this,
originalCallback = callback;
// wrap up the callback, so that, once it is fired, it is unbound
callback = function() {
self.off(event, callback, context);
originalCallback.apply(this, arguments);
};
return this.on.apply(this, [event, callback, context]);
}
};
function Observable() {
this.callbacks = this.callbacks || {};
}
Observable.prototype = {
/**
* Adds callback(s) for the given event(s).
* @param {String} events Whitespace-delimited list of event names
* @param {Function} fn Callback function
* @return {Object} Reference to this Observable
*/
on: function(events, fn) {
var self = this;
if (typeof fn === 'function') {
events.replace(/[^\s]+/g, function(name, pos) {
(self.callbacks[name] = self.callbacks[name] || []).push(fn);
});
}
return this;
},
/**
* Removes the given callback(s) for the given event(s). A wildcard value ("*")
* removes all events and their associated callbacks from the callbacks object.
* Specifying a fn parameter will remove that callback for the given event(s).
* Not specifying a fn parameter will result in all callbacks being removed for
* the given event(s).
* @param {String} events Whitespace-delimited list of event names
* @param {Function} fn (optional) Callback function
* @return {Object} Reference to this Observable
*/
off: function(events, fn) {
var i = 0,
arr,
callback,
self = this;
if (events === '*') {
this.callbacks = {};
} else if (fn) {
arr = this.callbacks[events];
for (i; (callback = arr && arr[i]); i++) {
if (callback === fn) {
arr.splice(i, 1);
}
}
} else if (typeof events === 'string') {
events.replace(/[^\s]+/g, function(name) {
self.callbacks[name] = [];
});
}
return this;
},
/**
* Same as Observable#on but callback function is only triggered a single
* time and is then removed from the callbacks object.
* @param {String} name Event name
* @param {Function} fn Callback function
* @return {Object} Reference to this Observable
*/
one: function(name, fn) {
if (fn) {
fn.one = true;
}
return this.on(name, fn);
},
/**
* Triggers all callbacks for a given event name.
* @param {String} name Event name
* @return {Object} Reference to this Observable
*/
trigger: function(name) {
var i = 0,
args = Array.prototype.slice.call(arguments, 1),
fn,
fns = this.callbacks[name] || [];
for (i; (fn = fns[i]); ++i) {
if (!fn.busy) {
fn.busy = true;
fn.apply(this, [name].concat(args));
if (fn.one) {
fns.splice(i, 1);
i--;
}
fn.busy = false;
}
}
return this;
}
};
define(
'EventEmitter',
function() {
var THIS_REGEX = /\bthis\b/,
slice = Array.prototype.slice;
function extend(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
/**
* Invokes a function with the given arguments and context
* @param {function} fn - Function to be invoked
* @param {array} args - Array of arguments to pass to the function
* @param {object} context - Context under which to invoke the function
* @returns {mixed} The return value of the invoked function
*/
function invoke(fn, args, context) {
return context ? fn.call(context, args) : fn(args);
}
/**
* Checks if all filters listed in target also exist in test.
* @param {array} target - List of filters to match
* @param {array} target - List of filters to test
*/
function matchFilters(target, test) {
var i,
j;
target.sort();
test.sort();
if (target.length <= test.length) {
// This for-loop is made possible by pre-sorting the filter arrays above
for (i = test.indexOf(target[0]), j = 0; i < test.length && j < target.length; i++) {
/*
* If the test string is less than the target string, this
* merely means we haven't started to any match yet and we
* we should increment the test index (i) but not the target
* index (j). We have no condition for this case, since the
* index for test will be incremented no matter what.
*
* If test and target strings are equal we can continue our
* match and increment the index for target (j), likewise the
* test index (i) will be implicitly incremeted at the end of
* the loop.
*/
if (test[i] === target[j]) {
j++;
}
/*
* If the test string is greater (alphabetically speaking)
* than the target string, it means we are no longer matching
* successfully and we should break.
*/
if(test[i] > target[j]) {
break;
}
}
// This is true only if all target filters were satisfied
return j === target.length;
}
return false;
}
/**
* Parses and iterates an events string
* @param {object} context - Context to be passed to the iterator function
* @param {string} events - Events string to be parsed and iterated
* @param {function} fn - Iterator function called for each event topic
* @param {mixed} args - Additional arguments to be passed to the iterator
* @returns {object} The object passed as the context
*/
function iterateEvents(context, events, fn, args) {
events = events.split(' ');
var event,
filters,
i;
for (i = 0; (event = events[i]); i++) {
filters = event.split('.');
fn(context, filters.shift(), filters, args);
}
return context;
}
/**
* Removes an event handler
* @param {string} events - Event topics (separated by spaces) and filters (separated by dots)
* @returns {object} The object from which the events were removed
*/
function off(events) {
return iterateEvents(this, events, _off);
}
function _off(context, topic, filters) {
topic = context._topics[topic];
var handler,
i;
if (topic) {
if (!filters.length) {
topic.length = 0;
} else {
for (i = 0; (handler = topic[i]); i++) {
if (matchFilters(filters, handler.filters)) {
topic.splice(j--, 1);
}
}
}
}
}
/**
* Attaches an event handler
* @param {string} events - Event topics (separated by spaces) and filters (separated by dots)
* @param {function} fn - Handler to be called when the event is triggered
* @returns {object} The object from which the events are attached
*/
function on(events, fn) {
return iterateEvents(this, events, _on, fn);
}
function _on(context, topic, filters, fn) {
if (!context._topics[topic]) {
context._topics[topic] = [];
}
context._topics[topic].push({
filters: filters,
fn: fn,
hasContext: THIS_REGEX.test(fn)
});
}
/**
* Attaches an event handler to be used only once
* @param {string} events - Event topics (separated by spaces) and filters (separated by dots)
* @param {function} fn - Handler to be called when the event is triggered
* @returns {object} The object from which the events are attached
*/
function one(events, fn) {
fn = function() {
this.off(fn);
fn.apply(this, arguments);
};
return on.apply(this, arguments);
}
/**
* Triggers all handlers associated with an event topic
* @param {string} topic - The name of the event topic to trigger
* @returns {object} The object from which the events are triggered
*/
function trigger(topic /* ... */) {
var handlers = this._topics[topic];
if (handlers) {
_trigger(this, handlers, slice.call(arguments, 1));
}
return this;
}
/**
* Calls a set of event handlers
* @param {object} context - The object which is firing the event
* @param {array} handlers - Array of the handlers to be invoked
* @param {array} args - Additional arguments to be passed to the handlers
*/
function _trigger(context, handlers, args) {
var handler,
stop,
i;
for (i = 0; (handler = handlers[i]); i++) {
if (invoke(handler.fn, args, handler.hasContext && context) === false) {
break;
}
}
}
/**
* Mixin for the eventing behavior
* @param {object} obj - Object to have on/one/off/trigger mixed in
* @returns {object} The updated object
*/
return function(obj) {
if (obj !== Object(obj)) {
// TODO throw a good error
}
extend(obj, {
_topics: obj._topics || {},
off: off,
on: on,
one: one,
trigger: trigger
});
return obj;
};
}
);
function define(name, fn) {
EventEmitter = fn();
}
</script>
var backboneObserver = new Observer();
var riotObserver = new Observable();
var eventEmitter = EventEmitter({});
function callback () {
var string = 'Some string to operate on.',
words = string.split(' '),
longestWord;
words.forEach(function(word) {
if (!longestWord || word.length < longestWord.length) {
longestWord = word;
}
});
console.log('Longest word is: %s', longestWord);
}
for (var i = 0; i < 20; i++) {
backboneObserver.on('twentylisteners', callback);
riotObserver.on('twentylisteners', callback);
eventEmitter.on('twentylisteners', callback);
}
backboneObserver.on('onelistener', callback);
riotObserver.on('onelistener', callback);
eventEmitter.on('onelistener', callback);
Ready to run.
Test | Ops/sec | |
---|---|---|
.trigger() Backbone |
| ready |
.trigger() Riot.js |
| ready |
.trigger() EventEmitter |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.