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
Changed
In this comparison I'm trying to focus on event inheritance feature present in PubSubJS and my implementation Peter Higgins´ Port from Dojo.
In most PubSub implementations Subscribers and Publishers have a one to one relationship. This gives them a huge performance benefit, but can be a drawback when building complex decoupled applications as you have to wire every single event. In this comparison I'm looking at a pretty standard GUI like the one on google.com. We have a HEADER region, a tool region to the LEFT, a CONTENT region and a FOOTER. Each one of these regions are dependent on each other using PubSub to communicate. A MANAGER in each region is responsible for Loading/Unloading modules. A typical PubSub message for this application would look something like this:
"/APP/REGION/MODULE/EVENT"
Using a PubSub implementation that allows for inheritance allow us to create a subscriber for "/APP/REGION" that would listen to ALL events that occur within this region. Using the simpler implementations we would have to publish two events, one for the module and one for the region.
In this example each PubSub implementation has 4 subscribers. One for APP, REGION, MODULE and EVENT each. PubSubJS and JQuery Subscriber may invoke all four subscriber callbacks by a single publication to app/region/module/event where as the rest each has to publish 4 events.
More info
Compared:
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js?fastfix=me"></script>
<script>
var jQueryFastfixed = jQuery.noConflict(true);
delete window.jQuery;
delete window.$;
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="//raw.github.com/hij1nx/EventEmitter2/master/lib/eventemitter2.js"></script>
<script src="//raw.github.com/pmelander/Subtopic/master/minified/subtopic.min.js"></script>
<script src="//raw.github.com/phiggins42/bloody-jquery-plugins/master/pubsub.js"></script>
<script src="//raw.github.com/appendto/amplify/master/src/core.js"></script>
<script src="//raw.github.com/spine/spine/dev/lib/spine.js"></script>
<script src="https://raw.github.com/rafikk/ply/master/src/core.js"></script>
<script src="//raw.github.com/mroderick/PubSubJS/master/src/pubsub.js"></script>
<script src="//raw.github.com/phiggins42/bloody-jquery-plugins/55e41df9bf08f42378bb08b93efcb28555b61aeb/pubsub.js"></script>
<script>
(function(window, $, undefined) {
// ## jquery/event/fastfix/fastfix.js
// http://bitovi.com/blog/2012/04/faster-jquery-event-fix.html
// https://gist.github.com/2377196
// IE 8 has Object.defineProperty but it only defines DOM Nodes. According to
// http://kangax.github.com/es5-compat-table/#define-property-ie-note
// All browser that have Object.defineProperties also support Object.defineProperty properly
if(Object.defineProperties) {
var
// Use defineProperty on an object to set the value and return it
set = function (obj, prop, val) {
if(val !== undefined) {
Object.defineProperty(obj, prop, {
value : val
});
}
return val;
},
// special converters
special = {
pageX : function (original) {
if(!original) {
return;
}
var eventDoc = this.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
return original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
},
pageY : function (original) {
if(!original) {
return;
}
var eventDoc = this.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
return original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
},
relatedTarget : function (original) {
if(!original) {
return;
}
return original.fromElement === this.target ? original.toElement : original.fromElement;
},
metaKey : function (originalEvent) {
if(!originalEvent) {
return;
}
return originalEvent.ctrlKey;
},
which : function (original) {
if(!original) {
return;
}
return original.charCode != null ? original.charCode : original.keyCode;
}
};
// Get all properties that should be mapped
$.each($.event.keyHooks.props.concat($.event.mouseHooks.props).concat($.event.props), function (i, prop) {
if (prop !== "target") {
(function () {
Object.defineProperty($.Event.prototype, prop, {
get : function () {
// get the original value, undefined when there is no original event
var originalValue = this.originalEvent && this.originalEvent[prop];
// overwrite getter lookup
return this['_' + prop] !== undefined ? this['_' + prop] : set(this, prop,
// if we have a special function and no value
special[prop] && originalValue === undefined ?
// call the special function
special[prop].call(this, this.originalEvent) :
// use the original value
originalValue)
},
set : function (newValue) {
// Set the property with underscore prefix
this['_' + prop] = newValue;
}
});
})();
}
});
$.event.fix = function (event) {
if (event[ $.expando ]) {
return event;
}
// Create a jQuery event with at minimum a target and type set
var originalEvent = event,
event = $.Event(originalEvent);
event.target = originalEvent.target;
// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
if (!event.target) {
event.target = originalEvent.srcElement || document;
}
// Target should not be a text node (#504, Safari)
if (event.target.nodeType === 3) {
event.target = event.target.parentNode;
}
return event;
}
}
})(this, jQueryFastfixed);
</script>
<script>
(function(context,OBJECT,NUMBER,LENGTH,toString,version,undefined,oldClass,jsface){function isMap(obj){return obj&&typeof obj===OBJECT&&!(typeof obj.length===NUMBER&&!obj.propertyIsEnumerable(LENGTH));}function isArray(obj){return obj&&typeof obj===OBJECT&&typeof obj.length===NUMBER&&!obj.propertyIsEnumerable(LENGTH);}function isFunction(obj){return obj&&typeof obj==="function";}function isFunction(obj){return obj&&typeof obj==="function";}function isString(obj){return toString.apply(obj)==="[object String]";}function isClass(obj){return isFunction(obj)&&obj.prototype&&obj===obj.prototype.constructor;}function copier(key,value,ignoredKeys,object,iClass,oPrototype){if(!ignoredKeys||!ignoredKeys.hasOwnProperty(key)){object[key]=value;if(iClass){oPrototype[key]=value;}}}function extend(object,subject,ignoredKeys){if(isArray(subject)){for(var len=subject.length;--len>=0;){extend(object,subject[len],ignoredKeys);}}else{ignoredKeys=ignoredKeys||{constructor:1,$super:1,prototype:1,$superb:1};var iClass=isClass(object),isSubClass=isClass(subject),oPrototype=object.prototype,supez,key,proto;if(isMap(subject)){for(key in subject){copier(key,subject[key],ignoredKeys,object,iClass,oPrototype);}}if(isSubClass){proto=subject.prototype;for(key in proto){copier(key,proto[key],ignoredKeys,object,iClass,oPrototype);}}if(iClass&&isSubClass){extend(oPrototype,subject.prototype,ignoredKeys);}}}function Class(parent,api){if(!api){parent=(api=parent,0);}var clazz,constructor,singleton,statics,key,bindTo,len,i=0,p,ignoredKeys={constructor:1,$singleton:1,$statics:1,prototype:1,$super:1,$superp:1,main:1},overload=Class.overload,plugins=Class.plugins;api=(typeof api==="function"?api():api)||{};constructor=api.hasOwnProperty("constructor")?api.constructor:0;singleton=api.$singleton;statics=api.$statics;for(key in plugins){ignoredKeys[key]=1;}clazz=singleton?{}:constructor?overload?overload("constructor",constructor):constructor:function(){};bindTo=singleton?clazz:clazz.prototype;parent=!parent||isArray(parent)?parent:[parent];len=parent&&parent.length;while(i<len){p=parent[i++];for(key in p){if(!ignoredKeys[key]){bindTo[key]=p[key];if(!singleton){clazz[key]=p[key];}}}for(key in p.prototype){if(!ignoredKeys[key]){bindTo[key]=p.prototype[key];}}}for(key in api){if(!ignoredKeys[key]){bindTo[key]=api[key];}}for(key in statics){clazz[key]=bindTo[key]=statics[key];}if(!singleton){p=parent&&parent[0]||parent;clazz.$super=p;clazz.$superp=p&&p.prototype?p.prototype:p;}for(key in plugins){plugins[key](clazz,parent,api);}if(isFunction(api.main)){api.main.call(clazz,clazz);}return clazz;}Class.plugins={};jsface={version:version,Class:Class,extend:extend,isMap:isMap,isArray:isArray,isFunction:isFunction,isString:isString,isClass:isClass};if(typeof module!=="undefined"&&module.exports){module.exports=jsface;}else{oldClass=context.Class;context.Class=Class;context.jsface=jsface;jsface.noConflict=function(){context.Class=oldClass;};}})(this,"object","number","length",Object.prototype.toString,"2.1.1");(function(context){var jsface=context.jsface,Class=jsface.Class,isFunction=jsface.isFunction,readyFns=[],readyCount=0;Class.plugins.$ready=function(clazz,parent,api){var r=api.$ready,len=parent?parent.length:0,count=len,pa,i,entry;while(len--){for(i=0;i<readyCount;i++){entry=readyFns[i];pa=parent[len];if(pa===entry[0]){entry[1].call(pa,clazz,parent,api);count--;}if(!count){break;}}}if(isFunction(r)){r.call(clazz,clazz,parent,api);readyFns.push([clazz,r]);readyCount++;}};})(this);var Utils=Class({$singleton:true,noop:function(){},asteriskEnd:function(string){return(string.slice(string.length-1)==="*");},removeAsteriskEnd:function(string){if(this.asteriskEnd(string)){return string.slice(0,string.length-1);}else{return false;}}});var PubSub2=Class(function(){var parseChannel=function(string){if(string.length){if(Utils.asteriskEnd(string)){return Utils.removeAsteriskEnd(string);}}};return{main:function(){this.modules=[];this.channels={};this.channelsList=[];this.subscriptions={};},$singleton:true,$statics:{async:function(fn){setTimeout(function(){fn();},0);},hasSubscribers:function(channel){return((this.channels[channel].subscribers).length>0);}},createChannel:function(channel){if(this.channels[channel]){return this;}else{this.channels[channel]=new Channel(channel,true);this.channelsList.push(channel);var len=this.channels[channel].subChannels.length,i=0;for(i=0;i<len;i++){if(!this.channels[this.channels[channel].subChannels[i]]){this.channels[this.channels[channel].subChannels[i]]=new Channel(this.channels[channel].subChannels[i],false);this.channelsList.push(this.channels[channel].subChannels[i]);}}return this;}},deliver:function(channel,data){var len=this.channels[channel].subscribers.length,i=0;for(i=0;i<len;i++){if(this.channels[channel].subscribers[i]){this.channels[channel].subscribers[i].callback(data);}}},publish:function(channel,data){var theChannel=this.channels[channel];if(theChannel.subscribers.length){var len=theChannel.subscribers.length,i;for(i=0;i<len;i++){if(theChannel.subscribers[i]){theChannel.subscribers[i].callback(data);}}if(theChannel.subChannels.length){len=theChannel.subChannels.length;for(i=0;i<len;i++){this.deliver(theChannel.subChannels[i],data);}}}},subscribe:function(channel,cb){if(Utils.asteriskEnd(channel)){channel=parseChannel(channel);}if(!this.channels[channel]){return null;}if(this.channels[channel].subscribers){this.channels[channel].subscribers.push({callback:cb});return this.channels[channel].subscribers.length-1;}else{return null;}},unsubscribe:function(channel,id){if(this.channels[channel].subscribers[id]){this.channels[channel].subscribers[id]=0;return this;}else{return this;}}};});var Channel=Class({constructor:function(channel,original){this.channel=channel;this.original=original;this.subscribers=[];this.splitter=":";if(this.original){this.subChannels=[];this.parseTopics();}},deleteChannel:function(){this.channel=null;this.original=null;this.subscribers=null;this.splitter=null;this.subChannels=null;this.parseTopics=null;this.clearSubs=null;this.changeChannel=null;return true;},clearSubs:function(){this.subscribers=[];},changeChannel:function(channel){PubSub2.publish("pubsub:channels:changeChannel",{prev:this.channel,next:channel},true);this.channel=channel;},parseTopics:function(){var colonIdx=this.channel.indexOf(":"),dotIdx=this.channel.indexOf(".");if(colonIdx>-1){this.splitter=":";}else{if(dotIdx>-1){this.splitter=".";}}var channelArr=this.channel.split(this.splitter);var len=channelArr.length,str="",i=0,x=0;for(i=0;i<(len-1);i++){str="";for(x=0;x<=i;x++){if(x===0){str=channelArr[x];}else{str=str+this.splitter+channelArr[x];}}this.subChannels.push(str);}}});
</script>
<script type="text/javascript">
var iter = 0,
callback = function () {
iter += 1;
},
payload = {
"somekey" : "somevalue"
},
x = 0,
id = 0,
id2 = 0,
noop = function () {void(0);},
emitter = null,
emitterWITHwildcard = null,
jQueryWindow = null,
jQueryFastfixedWindow = null;
jQuery(function () {
emitter = new EventEmitter2({
"wildcard" : false,
"delimiter" : "/",
"newListener" : false,
"maxListeners" : 0
});
emitterWildcard = new EventEmitter2({
"wildcard" : true,
"delimiter" : "/",
"newListener" : false,
"maxListeners" : 0
});
PubSub2.createChannel('topic2');
id = PubSub2.subscribe('topic2',noop);
PubSub2.createChannel('topic5');
PubSub2.createChannel('app');
PubSub2.subscribe('app',callback);
jQueryWindow = jQuery(window).on('app', callback);
jQueryFastfixedWindow = jQueryFastfixed(window).on('app',callback);
PubSub.subscribe('app', callback);
subtopic.subscribe('app', callback);
amplify.subscribe('app', callback);
Spine.bind('app', callback);
Ply.core.listen('app', callback);
emitter.on('app',callback);
emitterWildcard.on('app',callback);
});
</script>
Ready to run.
Test | Ops/sec | |
---|---|---|
jQuery Events |
| ready |
jQuery Events (Fastfixed) |
| ready |
PubSubJS - async |
| ready |
PubSubJS - sync |
| ready |
Pure JS PubSub |
| ready |
Amplify Pub/Sub |
| ready |
Spine Events |
| ready |
Ply Notify/Listen |
| ready |
Subtopic |
| ready |
My-PubSub part of ControllerJS on github |
| ready |
EventEmitter2 |
| ready |
EventEmitter2 with wildcard |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.