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
What is the fastest implementation for bindonce directives?
This test is trying to prove what is the quickest way to set up bindings. It uses ngRepeat in every test, so the slowness of ngRepeat is negated. The only difference in each test is how the bindings are created.
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular.min.js"></script>
<div id="myApp" ng-app="OneBinders" ng-controller="oneBindCtrl">
<div bindonce ng-repeat="place in places">
<a bo-href="place.src"><span bo-text="place.title"></span></a>
</div>
<div ng-repeat="place in places2">
<a bb-one-bind-href="place.src"><span bb-one-bind-text="place.title"></span></a>
</div>
<div ng-repeat="place in places3">
<a ng-href="{{place.src}}">{{place.title}}</a>
</div>
<div ng-repeat="place in places4">
<a once-href="place.src"><span once-text="place.title"></span></a>
</div>
<div ng-repeat="place in places5">
<a set-href="place.src"><span set-text="place.title"></span></a>
</div>
<div ng-repeat="place in places6">
<a ng-href="{{::place.src}}">{{::place.title}}</a>
</div>
</div>
<script>
var oneBinders = angular.module('OneBinders', ['pasvaz.bindonce', 'once', 'watchFighters'])
.controller('oneBindCtrl', function($scope, $rootScope){
$rootScope.places = [];
$rootScope.places2 = [];
$rootScope.places3 = [];
$rootScope.places4 = [];
$rootScope.places5 = [];
$rootScope.places6 = [];
});
oneBinders.service('BindOnceService', function($rootScope) {
this.setTheValues = function(array,iterations){
for(var i = 0;i < iterations;i++){
$rootScope[array].push({title: 'Atlanta', src: 'http://upload.wikimedia.org/wikipedia/commons/a/a7/Atlanta_Skyline_from_Buckhead.jpg'});
$rootScope[array].push({title: 'Los Angelas', src: 'http://www.wildnatureimages.com/images%202/060310-167..jpg'});
$rootScope[array].push({title: 'New York', src: 'http://dormroomfund.com/img/slider-images/new-york-city.jpg'});
}
}
});
angular.forEach([{tag: 'Src', method: 'attr'}, {tag: 'Text', method: 'text'},
{tag: 'Href', method: 'attr'}, {tag: 'Class', method: 'addClass'},
{tag: 'Html', method: 'html'}, {tag: 'Alt', method: 'attr'},
{tag: 'Style', method: 'css'}, {tag: 'Value', method: 'attr'},
{tag: 'Id', method: 'attr'}, {tag: 'Title', method: 'attr'}], function(v){
var directiveName = 'bbOneBind'+v.tag;
oneBinders.directive(directiveName, function(){
return {
restrict: 'EA',
link: function(scope, element, attrs){
var rmWatcher = scope.$watch(attrs[directiveName], function(newV,oldV){
if(newV){
if(v.method === 'attr'){
element[v.method](v.tag.toLowerCase(),newV);
} else {
element[v.method](newV);
}
rmWatcher();
}
});
}
};
});
});
(function () {
"use strict";
/**
* Bindonce - Zero watches binding for AngularJs
* @version v0.3.1
* @link https://github.com/Pasvaz/bindonce
* @author Pasquale Vazzana <pasqualevazzana@gmail.com>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
var bindonceModule = angular.module('pasvaz.bindonce', []);
bindonceModule.directive('bindonce', function ()
{
var toBoolean = function (value)
{
if (value && value.length !== 0)
{
var v = angular.lowercase("" + value);
value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]');
}
else
{
value = false;
}
return value;
};
var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
if (isNaN(msie))
{
msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
}
var bindonceDirective =
{
restrict: "AM",
controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate)
{
var showHideBinder = function (elm, attr, value)
{
var show = (attr === 'show') ? '' : 'none';
var hide = (attr === 'hide') ? '' : 'none';
elm.css('display', toBoolean(value) ? show : hide);
};
var classBinder = function (elm, value)
{
if (angular.isObject(value) && !angular.isArray(value))
{
var results = [];
angular.forEach(value, function (value, index)
{
if (value) results.push(index);
});
value = results;
}
if (value)
{
elm.addClass(angular.isArray(value) ? value.join(' ') : value);
}
};
var transclude = function (transcluder, scope)
{
transcluder.transclude(scope, function (clone)
{
var parent = transcluder.element.parent();
var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
angular.forEach(clone, function (node)
{
parentNode.insertBefore(node, afterNextSibling);
});
});
};
var ctrl =
{
watcherRemover: undefined,
binders: [],
group: $attrs.boName,
element: $element,
ran: false,
addBinder: function (binder)
{
this.binders.push(binder);
// In case of late binding (when using the directive bo-name/bo-parent)
// it happens only when you use nested bindonce, if the bo-children
// are not dom children the linking can follow another order
if (this.ran)
{
this.runBinders();
}
},
setupWatcher: function (bindonceValue)
{
var that = this;
this.watcherRemover = $scope.$watch(bindonceValue, function (newValue)
{
if (newValue === undefined) return;
that.removeWatcher();
that.checkBindonce(newValue);
}, true);
},
checkBindonce: function (value)
{
var that = this, promise = (value.$promise) ? value.$promise.then : value.then;
// since Angular 1.2 promises are no longer
// undefined until they don't get resolved
if (typeof promise === 'function')
{
promise(function ()
{
that.runBinders();
});
}
else
{
that.runBinders();
}
},
removeWatcher: function ()
{
if (this.watcherRemover !== undefined)
{
this.watcherRemover();
this.watcherRemover = undefined;
}
},
runBinders: function ()
{
while (this.binders.length > 0)
{
var binder = this.binders.shift();
if (this.group && this.group != binder.group) continue;
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
switch (binder.attr)
{
case 'boIf':
if (toBoolean(value))
{
transclude(binder, binder.scope.$new());
}
break;
case 'boSwitch':
var selectedTranscludes, switchCtrl = binder.controller[0];
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?']))
{
binder.scope.$eval(binder.attrs.change);
angular.forEach(selectedTranscludes, function (selectedTransclude)
{
transclude(selectedTransclude, binder.scope.$new());
});
}
break;
case 'boSwitchWhen':
var ctrl = binder.controller[0];
ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []);
ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element });
break;
case 'boSwitchDefault':
var ctrl = binder.controller[0];
ctrl.cases['?'] = (ctrl.cases['?'] || []);
ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element });
break;
case 'hide':
case 'show':
showHideBinder(binder.element, binder.attr, value);
break;
case 'class':
classBinder(binder.element, value);
break;
case 'text':
binder.element.text(value);
break;
case 'html':
binder.element.html(value);
break;
case 'style':
binder.element.css(value);
break;
case 'src':
binder.element.attr(binder.attr, value);
if (msie) binder.element.prop('src', value);
break;
case 'attr':
angular.forEach(binder.attrs, function (attrValue, attrKey)
{
var newAttr, newValue;
if (attrKey.match(/^boAttr./) && binder.attrs[attrKey])
{
newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
newValue = binder.scope.$eval(binder.attrs[attrKey]);
binder.element.attr(newAttr, newValue);
}
});
break;
case 'href':
case 'alt':
case 'title':
case 'id':
case 'value':
binder.element.attr(binder.attr, value);
break;
}
}
this.ran = true;
}
};
return ctrl;
}],
link: function (scope, elm, attrs, bindonceController)
{
var value = attrs.bindonce && scope.$eval(attrs.bindonce);
if (value !== undefined)
{
bindonceController.checkBindonce(value);
}
else
{
bindonceController.setupWatcher(attrs.bindonce);
elm.bind("$destroy", bindonceController.removeWatcher);
}
}
};
return bindonceDirective;
});
angular.forEach(
[
{ directiveName: 'boShow', attribute: 'show' },
{ directiveName: 'boHide', attribute: 'hide' },
{ directiveName: 'boClass', attribute: 'class' },
{ directiveName: 'boText', attribute: 'text' },
{ directiveName: 'boBind', attribute: 'text' },
{ directiveName: 'boHtml', attribute: 'html' },
{ directiveName: 'boSrcI', attribute: 'src', interpolate: true },
{ directiveName: 'boSrc', attribute: 'src' },
{ directiveName: 'boHrefI', attribute: 'href', interpolate: true },
{ directiveName: 'boHref', attribute: 'href' },
{ directiveName: 'boAlt', attribute: 'alt' },
{ directiveName: 'boTitle', attribute: 'title' },
{ directiveName: 'boId', attribute: 'id' },
{ directiveName: 'boStyle', attribute: 'style' },
{ directiveName: 'boValue', attribute: 'value' },
{ directiveName: 'boAttr', attribute: 'attr' },
{ directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 },
{ directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } },
{ directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch', },
{ directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch', }
],
function (boDirective)
{
var childPriority = 200;
return bindonceModule.directive(boDirective.directiveName, function ()
{
var bindonceDirective =
{
priority: boDirective.priority || childPriority,
transclude: boDirective.transclude || false,
terminal: boDirective.terminal || false,
require: ['^bindonce'].concat(boDirective.require || []),
controller: boDirective.controller,
compile: function (tElement, tAttrs, transclude)
{
return function (scope, elm, attrs, controllers)
{
var bindonceController = controllers[0];
var name = attrs.boParent;
if (name && bindonceController.group !== name)
{
var element = bindonceController.element.parent();
bindonceController = undefined;
var parentValue;
while (element[0].nodeType !== 9 && element.length)
{
if ((parentValue = element.data('$bindonceController'))
&& parentValue.group === name)
{
bindonceController = parentValue;
break;
}
element = element.parent();
}
if (!bindonceController)
{
throw new Error("No bindonce controller: " + name);
}
}
bindonceController.addBinder(
{
element: elm,
attr: boDirective.attribute || boDirective.directiveName,
attrs: attrs,
value: attrs[boDirective.directiveName],
interpolate: boDirective.interpolate,
group: name,
transclude: transclude,
controller: controllers.slice(1),
scope: scope
});
};
}
};
return bindonceDirective;
});
})
})();
/**
* angular-once - one time bindings for AngularJS
* @version v0.1.7
* @link https://github.com/tadeuszwojcik/angular-once
* @author Tadeusz Wójcik <tadeuszwojcik@gmail.com>
* @license WTFPL License, https://github.com/tadeuszwojcik/angular-once/blob/master/LICENSE.txt
*/
(function (window, angular, undefined) {
'use strict';
function setOneTimeBinding($scope, element, watch, watcherParser, bindingParser, done) {
// get value to watch
var watchingValue = watcherParser($scope);
// if we have a valid value, render the binding's value
if (watchingValue !== undefined) {
// if watching and binding $parsers are the same, use watching's value, else $parse the new value
return done(element, watcherParser == bindingParser ? watchingValue : bindingParser($scope));
}
// we do not have a valid value, so we register a $watch
var watcherRemover = $scope.$watch(watch, function (newValue) {
// wait until we have a valid value
if (newValue == undefined) return;
// remove this $watch
removeWatcher();
// if watching and binding $parsers are the same, use watching's value, else $parse the new value
return done(element, watcherParser == bindingParser ? newValue : bindingParser($scope));
});
function removeWatcher() {
if (watcherRemover) {
watcherRemover();
}
}
$scope.$on("$destroy", removeWatcher);
}
var once = angular.module('once', []);
function makeBindingDirective(definition) {
once.directive(definition.name, ['$parse', function ($parse) {
return function ($scope, element, attrs) {
var watch = attrs.onceWaitFor || attrs[definition.name];
var watcherParser = $parse(watch);
var bindingParser = attrs.onceWaitFor ? $parse(attrs[definition.name]) : watcherParser;
setOneTimeBinding($scope, element, watch, watcherParser, bindingParser, definition.binding);
};
}]);
}
var bindingsDefinitions = [
{
name: 'onceText',
binding: function (element, value) {
element.text(value !== null ? value : "");
}
},
{
name: 'onceHtml',
binding: function (element, value) {
element.html(value);
}
},
{
name: 'onceSrc',
binding: function (element, value) {
element.attr('src', value);
}
},
{
name: 'onceHref',
binding: function (element, value) {
element.attr('href', value);
}
},
{
name: 'onceTitle',
binding: function (element, value) {
element.attr('title', value);
}
},
{
name: 'onceAlt',
binding: function (element, value) {
element.attr('alt', value);
}
},
{
name: 'onceId',
binding: function (element, value) {
element.attr('id', value);
}
},
{
name: 'onceIf',
binding: function (element, value) {
if (!value) {
element.remove();
}
}
},
{
name: 'onceClass',
binding: function (element, value) {
if (angular.isObject(value) && !angular.isArray(value)) {
var results = [];
angular.forEach(value, function (val, index) {
if (val) results.push(index);
});
value = results;
}
if (value) {
element.addClass(angular.isArray(value) ? value.join(' ') : value);
}
}
},
{
name: 'onceStyle',
binding: function (element, value) {
element.css(value);
}
},
{
name: 'onceShow',
binding: function (element, value) {
if (value) {
element.css('display', '');
} else {
element.css('display', 'none');
}
}
},
{
name: 'onceHide',
binding: function (element, value) {
if (value) {
element.css('display', 'none');
} else {
element.css('display', '');
}
}
}
];
angular.forEach(bindingsDefinitions, makeBindingDirective);
once.directive('once', function () {
return function ($scope, element, attrs) {
angular.forEach(attrs, function (attr, attrName) {
if (!/^onceAttr[A-Z]/.test(attrName)) return;
var bind = function(element, value) {
var dashedName = attrName.replace(/[A-Z]/g, function(match) { return '-' + match.toLowerCase(); });
var name = dashedName.substr(10);
element.attr(name, value);
};
setOneTimeBinding($scope, element, attrs, attrName, bind);
});
};
});
})(window, window.angular);
(function () {
"use strict";
angular.module('watchFighters', [])
.directive('setIf', [function () {
return {
transclude: 'element',
priority: 1000,
terminal: true,
restrict: 'A',
compile: function (element, attr, linker) {
return function (scope, iterStartElement, attr) {
iterStartElement[0].doNotMove = true;
var expression = attr.setIf;
var value = scope.$eval(expression);
if (value) {
linker(scope, function (clone) {
iterStartElement.after(clone);
});
}
};
}
};
}])
.directive('setHtml', function() {
return {
restrict: "A",
priority: 100,
link: function($scope, $el, $attr) {
$($el).html($scope.$eval($attr.setHtml));
}
};
})
.directive('setText', function() {
return {
restrict: "A",
priority: 100,
link: function($scope, $el, $attr) {
$($el).text($scope.$eval($attr.setText));
}
};
})
.directive('setClass', function() {
return {
restrict: "A",
priority: 100,
link: function($scope, $el, $attr) {
var classVal = $scope.$eval($attr.setClass);
if (angular.isObject(classVal)) {
for (var key in classVal) {
if (classVal.hasOwnProperty(key) && classVal[key]) {
$el.addClass(key);
}
}
} else {
$el.addClass(classVal);
}
}
};
})
.directive('setTitle', function() {
return {
restrict: "A",
priority: 100,
link: function($scope, $el, $attr) {
$($el).attr('title', $scope.$eval($attr.setTitle));
}
};
})
.directive('setHref', function() {
return {
restrict: "A",
priority: 100,
link: function($scope, $el, $attr) {
$($el).attr('href', $scope.$eval($attr.setHref));
}
};
})
;
})();
</script>
var i = angular.element(document.getElementById('myApp')).injector();
var s = i.get('BindOnceService');
var scope = i.get('$rootScope');
scope.places = [];
scope.places2 = [];
scope.places3 = [];
scope.places4 = [];
scope.places5 = [];
scope.places6 = [];
scope.$apply();
Ready to run.
Test | Ops/sec | |
---|---|---|
https://github.com/Pasvaz/bindonce |
| ready |
https://github.com/angular/angular.js/pull/6284 |
| ready |
Regular Angular |
| ready |
https://github.com/tadeuszwojcik/angular-once |
| ready |
https://github.com/abourget/abourget-angular |
| ready |
Angular 1.3 BindOnce |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.