jQuery vs JavaScript Performance Comparison

JavaScript performance comparison

Revision 115 of this test case created by

Preparation code

<div id="hello"></div>
<div class="bye"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
(function() {

	"use strict";
	
	var arr = [],
		slice = arr.slice,
		splice = arr.splice;
	
	window.Select = function(selector) {
		
		return new Select.init(selector);
		
	};
	
	Select.init = function(selector) {
		
		var length = 0,
			selectorArray = selector.split(',');
		
		for (var i = 0, len = selectorArray.length; i < len; i++) {
			
			var _selector = selectorArray[i],
				elements;
			
			// match a selector which is either a single id, class, or tag.
			// TODO: optimize regex further
			if (/^[\.#a-z][\w-]+$/i.test(_selector)) {
				
				switch (_selector[0]) {
					
					case '.':
						elements = slice.call(document.getElementsByClassName(_selector.substring(1)));
						break;
				
					case '#':
						elements = [document.getElementById(_selector.substring(1))];
						break;
						
					default:
						elements = slice.call(document.getElementsByTagName(_selector));	
						
				}
				
			} else {
				
				elements = slice.call(document.querySelectorAll(selector));
				
			}
			
			
			for (var k = 0, _len = elements.length; k < _len; k++) {
				
				this[k + length] = elements[k];
				
			}
			
			length += _len
			
		}
		
		this.selector = selector;
		
		this['length'] = length;
		
	};
	
	// TODO: What exactly is happening?
	// find better way to instantiate the Select constructor
	// we want to offer an interface which dpesn't necessitate the use of the new operator.
	Select.init.prototype = Select.prototype;
	
	Select.prototype.splice = function() {
		
		splice.apply(this, arguments);
		
		return this;
		
	};
	
	// adds one or more classes seperated by spaces
	Select.prototype.addClass = function(classNames) {
		
		classNames = classNames.split(' ');
		
		for (var i = 0, length = this.length; i < length; i++) {
			
			var element = this[i],
				className = element.className;
			
			for (var k = 0, len = classNames.length; k < len;k++) {
					
				if (!Select.hasClass(element, classNames[k])) {
				
					className += ' ' + classNames[k];
					
				}	
				
			}
			
			element.className = className;
		
		}
		
		return this;
		
	};
	
	// remove one or more classes seperated by spaces
	Select.prototype.removeClass = function(classNames) {
		
		classNames = classNames.split(' ');
		
		for (var i = 0, length = this.length; i < length; i++) {
		
			var element = this[i],
				className = element.className;
			
			for (var k = 0, len = classNames.length; k < len; k++) {
				
				className = className.replace(new RegExp('(?:^|\\s)' + classNames[k] + '(?!\\S)') ,'');
				
			}
			
			element.className = className;
			
		}
		
		return this;
		
	};
	
	// returns true if any of the elements in the collection matches the className
	Select.prototype.hasClass = function(className) {
		
		for (var i = 0, length = this.length; i < length; i++) {
			
			if (Select.hasClass(this[i], className)) {
				
				return true;
				
			}
			
		}
		
		return false;
		
	};
	
	Select.hasClass = function(element, className) {
		
		return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
		
	};
	
	Select.prototype.css = function(rules) {
			
		for (var i = 0, length = this.length; i < length; i++) {

			Select.css(this[i], rules);
			
		}
		
		return this;
		
	}
	
	Select.css = function(element, rules) {
		
		if (rules instanceof Array) {
		
			var ret = Object.create(null);
		
			var styles = getComputedStyle(element);
			
			for (var i = 0, length = rules.length; i < length; i++) {
				
				var property = rules[i];
					
				ret[property] = styles.getPropertyValue(property);
				
			}
			
			return ret;
			
		} else {

			for (var property in rules) {
										
				element.style[property] = rules[property];
				
			}
			
		}
		
	};
	
	// split a css value into an array split by value and postfix. Example: '10 px' -> [10, 'px']
	// works only for simple values and not for shorthand syntax or functions. Use only for splitting percent and px values
	Select.css.splitValues = function(rules) {
		
		for (var property in rules) {
			
			var propertyValue = rules[property],
				val = parseInt(propertyValue),
				match = typeof propertyValue === 'string' ? propertyValue.match(/[a-z]+/i) : null,
				postFix = match ? match[0] : match;
			
			rules[property] = [val, postFix];
			
		}
		
		return rules;

		
	}
	
	Select.prototype.attribute = function(name, value) {
		
		if (value) {
		
			for (var i = 0, length = this.length; i < length; i++) {
				
				this[i].setAttribute(name, value);
				
			}
			
			return this;
			
		} else {
			
			return this[0].getAttribute(name);
			
		}
		
	};
	
	Select.prototype.animate = function(props, options, callback) {
		
		for (var i = 0, length = 1; i < length; i++) {
			
			Select.animate(this[i], props, options, callback)
			
		}
		
	}
	
	// TODO: polyfill requestAnimationFrame for Android < 4.4: http://caniuse.com/#feat=requestanimationframe
	Select.animate = function(element, props, options, callback) {
		
		var self = this;
		
		props = Select.css.splitValues(props);
		
		options = Select.extend({
			duration: 400,
			transitionTimingFunction: 'linear'
		}, options)
				
		var propNames = Object.keys(props),
			initialProps = Select.css.splitValues(Select.css(element, propNames)),
			bezierCurve = new Select.animate.bezier(options.transitionTimingFunction, options.duration),
			totalElapsed = 0,
			lastIteration;
		
		var interval = function() {
			
			var now = Date.now(),
				elapsed = lastIteration ? now - lastIteration : 16;
			
			totalElapsed = Math.min(totalElapsed + elapsed, options.duration);
			
			for (var prop in initialProps) {
				
				var rawVal = initialProps[prop],
					val = rawVal[0],
					postFix = rawVal[1],
					difference = props[prop][0] - val,
					increment = Math.round(difference * bezierCurve.solve(totalElapsed / options.duration));
				
				element.style[prop] = (val + increment) + (postFix ? postFix : '');
				
			}
			
			lastIteration = now;
			
			if (increment !== difference) {
				
				requestAnimationFrame(interval)
				
			} else if (callback) {
				
				callback();
				
			}
		
		}	
			
		requestAnimationFrame(interval);
		
		return self;
		
	};
	
	// port of Webkit: http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
	// conforming the syntax was easy
	// TODO: understand how the formula works...
	Select.animate.bezier = function(points, duration) {
		
		if (typeof points === 'string') {
			points = Select.animate.bezier.transtionTimingFunctions[points];
		}
		
		this.cx = 3 * points[0];
		this.bx = 3 * (points[2] - points[0]) - this.cx;
		this.ax = 1 - this.cx - this.bx;
         
		this.cy = 3 * points[1];
		this.by = 3 * (points[3] - points[1]) - this.cy;
		this.ay = 1 - this.cy - this.by;
		
		this.epsilon = 1 / (200 * duration);
		
	};
	
	Select.animate.bezier.prototype.sampleCurveX = function(t) {
		 return ((this.ax * t + this.bx) * t + this.cx) * t;
	}
	
	Select.animate.bezier.prototype.sampleCurveY = function(t) {
		return ((this.ay * t + this.by) * t + this.cy) * t;
	}
	
	Select.animate.bezier.prototype.sampleCurveDerivativeX = function(t) {
		return (3 * this.ax * t + 2 * this.bx) * t + this.cx;
	}
	
	Select.animate.bezier.prototype.solveCurveX = function(x) {
		
		var self = this,
			t0,
			t1,
        	t2,
        	x2,
        	d2,
        	i;
		
		for (t2 = x, i = 0; i < 8; i++) {
            x2 = self.sampleCurveX(t2) - x;
            if (Math.abs(x2) < self.epsilon) {
                return t2;
            } 
            d2 = self.sampleCurveDerivativeX(t2);
            if (Math.abs(d2) < 1e-6) {
                break;
            } 
            t2 = t2 - x2 / d2;
        }
		
		t0 = 0;
        t1 = 1;
        t2 = x;

        if (t2 < t0)
            return t0;
        if (t2 > t1)
        	return t1;

        while (t0 < t1) {
            x2 = self.sampleCurveX(t2);
            if (Math.abs(x2 - x) < self.epsilon)
                return t2;
            if (x > x2)
                t0 = t2;
            else
                t1 = t2;
            t2 = (t1 - t0) * .5 + t0;
        }

        // Failure.
        return t2;
		
	};
	
	Select.animate.bezier.prototype.solve = function(x) {
        return this.sampleCurveY(this.solveCurveX(x));
    };
	
	// Defined according to the default css timing functions: https://www.w3.org/TR/css3-transitions/#transition-timing-function
	Select.animate.bezier.transtionTimingFunctions = {
		'ease': [0.25, 0.1, 0.25, 1],
		'linear': [0, 0, 1, 1],
		'ease-in': [0.42, 0, 1, 1],
		'ease-out': [0, 0, 0.58, 1],
		'ease-in-out': [0.42, 0, 0.58, 1]
	};
	
	// tests if the first element matches the selector
	Select.prototype.matches = function(selector) {
			
		return Select.matches(this[0], selector)
		
	};
	
	// test if an element matches a selector
	Select.matches = function(element, selector) {
		
		var matches = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.msMatchesSelector || element.oMatchesSelector;
		
		return matches.call(element, selector);
		
	};
	
	// accepts up to three arguments: event, delegateTarget, callback
	// if the number of arguments is three, the second argument is the delegateSelector
	Select.prototype.on = function(event) {
		
		var callback,
			delegateSelector;
		
		if (arguments.length === 3) {
			
			delegateSelector = arguments[1];
			
			var secondCallback = arguments[2];
			
			callback = function(event) {
				
				if (Select.matches(event.target, delegateSelector)) {
					
					secondCallback(event);
					
				}
				
			}
		
		} else {
			
			callback = arguments[1];
			
		}
		
		for (var i = 0, length = this.length; i < length; i++) {
			
			Select.on(this[i], event, callback);
			
		}	
		
	};
	
	Select.on = function(element, event, callback) {
		
		element.addEventListener(event, function(event) {
				
			// use IE's non-standard window.event object when the regular event object is not available
			event = event || window.event;
			
			callback(event);
			
		});	
		
	};
	
	Select.window = {
		scrollTop: function(target, options, callback) {
			
			Select.extend({
				duration: 0,
				transitionTimingFunction: 'linear'
			}, options);
			
			var bezierCurve = new Select.animate.bezier(options.transitionTimingFunction, options.duration),
				start = window.pageYOffset,
				difference = target - start,
				lastIteration,
				totalElapsed = 0;
			
			var interval = function() {
				
				var now = Date.now(),
					elapsed = lastIteration ? now - lastIteration : 16;
				
				lastIteration = now;	
				
				totalElapsed = Math.min(totalElapsed + elapsed, options.duration);
				
				var offsetTop = start + Math.round(difference * bezierCurve.solve(totalElapsed / options.duration));
				
				window.scrollTo(window.pageXOffset, offsetTop);
				
				if (target !== offsetTop) {
					
					requestAnimationFrame(interval)
					
				} else if (callback) {
					
					callback();
					
				}
				
			}
				
			requestAnimationFrame(interval);
			
		},
		width: function() {
			
			return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
			
		},
		height: function() {
			
			return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
			
		}
	}
	
	// merge two or more simple objects, provided as seperate arguments
	Select.extend = function() {
		
		var base = arguments[0];
		
		for (var i = 1, length = arguments.length; i < length; i++) {
			
			var options = arguments[i];
			
			for (name in options) {
				
				base[name] = options[name];
				
			}
			
		}
		
		return base;
		
	};
	
	Select.ajax = function(options) {
		
		var xhr = new XMLHttpRequest(),
			options = Select.extend({
				type: 'GET',
				url: location.href,
				data: null,
				dataType: 'application/json',
				success: null,
				error: null,
				timeout: 0
			}, options);
		
		xhr.open(options.type, options.url, true);
		
		xhr.setRequestHeader('Content-Type', options.dataType);
			
		xhr.onreadystatechange = function() {
			
			if (xhr.readyState === 4) {
				
				if (xhr.status === 200) {
					
					if (options.success) {
						
						var data = xhr.responseText;
						
						if (options.dataType === 'application/json') {
							
							data = JSON.parse(data);
							
						}
						
						options.success(data);
						
					}				
					
				} else {
					
					if (options.error) {
						
						options.error(xhr.status, xhr.statusText);
						
					}		
					
				}
				
			}
			
		};
		
		xhr.timeout = options.timeout;
		
		xhr.send(options.data);
		
	};
	
})();
</script>

    

Preparation code output

<div id="hello"></div> <div class="bye"></div> <script> (function() { "use strict"; var arr = [], slice = arr.slice, splice = arr.splice; window.Select = function(selector) { return new Select.init(selector); }; Select.init = function(selector) { var length = 0, selectorArray = selector.split(','); for (var i = 0, len = selectorArray.length; i < len; i++) { var _selector = selectorArray[i], elements; // match a selector which is either a single id, class, or tag. // TODO: optimize regex further if (/^[\.#a-z][\w-]+$/i.test(_selector)) { switch (_selector[0]) { case '.': elements = slice.call(document.getElementsByClassName(_selector.substring(1))); break; case '#': elements = [document.getElementById(_selector.substring(1))]; break; default: elements = slice.call(document.getElementsByTagName(_selector)); } } else { elements = slice.call(document.querySelectorAll(selector)); } for (var k = 0, _len = elements.length; k < _len; k++) { this[k + length] = elements[k]; } length += _len } this.selector = selector; this['length'] = length; }; // TODO: What exactly is happening? // find better way to instantiate the Select constructor // we want to offer an interface which dpesn't necessitate the use of the new operator. Select.init.prototype = Select.prototype; Select.prototype.splice = function() { splice.apply(this, arguments); return this; }; // adds one or more classes seperated by spaces Select.prototype.addClass = function(classNames) { classNames = classNames.split(' '); for (var i = 0, length = this.length; i < length; i++) { var element = this[i], className = element.className; for (var k = 0, len = classNames.length; k < len;k++) { if (!Select.hasClass(element, classNames[k])) { className += ' ' + classNames[k]; } } element.className = className; } return this; }; // remove one or more classes seperated by spaces Select.prototype.removeClass = function(classNames) { classNames = classNames.split(' '); for (var i = 0, length = this.length; i < length; i++) { var element = this[i], className = element.className; for (var k = 0, len = classNames.length; k < len; k++) { className = className.replace(new RegExp('(?:^|\\s)' + classNames[k] + '(?!\\S)') ,''); } element.className = className; } return this; }; // returns true if any of the elements in the collection matches the className Select.prototype.hasClass = function(className) { for (var i = 0, length = this.length; i < length; i++) { if (Select.hasClass(this[i], className)) { return true; } } return false; }; Select.hasClass = function(element, className) { return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className); }; Select.prototype.css = function(rules) { for (var i = 0, length = this.length; i < length; i++) { Select.css(this[i], rules); } return this; } Select.css = function(element, rules) { if (rules instanceof Array) { var ret = Object.create(null); var styles = getComputedStyle(element); for (var i = 0, length = rules.length; i < length; i++) { var property = rules[i]; ret[property] = styles.getPropertyValue(property); } return ret; } else { for (var property in rules) { element.style[property] = rules[property]; } } }; // split a css value into an array split by value and postfix. Example: '10 px' -> [10, 'px'] // works only for simple values and not for shorthand syntax or functions. Use only for splitting percent and px values Select.css.splitValues = function(rules) { for (var property in rules) { var propertyValue = rules[property], val = parseInt(propertyValue), match = typeof propertyValue === 'string' ? propertyValue.match(/[a-z]+/i) : null, postFix = match ? match[0] : match; rules[property] = [val, postFix]; } return rules; } Select.prototype.attribute = function(name, value) { if (value) { for (var i = 0, length = this.length; i < length; i++) { this[i].setAttribute(name, value); } return this; } else { return this[0].getAttribute(name); } }; Select.prototype.animate = function(props, options, callback) { for (var i = 0, length = 1; i < length; i++) { Select.animate(this[i], props, options, callback) } } // TODO: polyfill requestAnimationFrame for Android < 4.4: http://caniuse.com/#feat=requestanimationframe Select.animate = function(element, props, options, callback) { var self = this; props = Select.css.splitValues(props); options = Select.extend({ duration: 400, transitionTimingFunction: 'linear' }, options) var propNames = Object.keys(props), initialProps = Select.css.splitValues(Select.css(element, propNames)), bezierCurve = new Select.animate.bezier(options.transitionTimingFunction, options.duration), totalElapsed = 0, lastIteration; var interval = function() { var now = Date.now(), elapsed = lastIteration ? now - lastIteration : 16; totalElapsed = Math.min(totalElapsed + elapsed, options.duration); for (var prop in initialProps) { var rawVal = initialProps[prop], val = rawVal[0], postFix = rawVal[1], difference = props[prop][0] - val, increment = Math.round(difference * bezierCurve.solve(totalElapsed / options.duration)); element.style[prop] = (val + increment) + (postFix ? postFix : ''); } lastIteration = now; if (increment !== difference) { requestAnimationFrame(interval) } else if (callback) { callback(); } } requestAnimationFrame(interval); return self; }; // port of Webkit: http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h // conforming the syntax was easy // TODO: understand how the formula works... Select.animate.bezier = function(points, duration) { if (typeof points === 'string') { points = Select.animate.bezier.transtionTimingFunctions[points]; } this.cx = 3 * points[0]; this.bx = 3 * (points[2] - points[0]) - this.cx; this.ax = 1 - this.cx - this.bx; this.cy = 3 * points[1]; this.by = 3 * (points[3] - points[1]) - this.cy; this.ay = 1 - this.cy - this.by; this.epsilon = 1 / (200 * duration); }; Select.animate.bezier.prototype.sampleCurveX = function(t) { return ((this.ax * t + this.bx) * t + this.cx) * t; } Select.animate.bezier.prototype.sampleCurveY = function(t) { return ((this.ay * t + this.by) * t + this.cy) * t; } Select.animate.bezier.prototype.sampleCurveDerivativeX = function(t) { return (3 * this.ax * t + 2 * this.bx) * t + this.cx; } Select.animate.bezier.prototype.solveCurveX = function(x) { var self = this, t0, t1, t2, x2, d2, i; for (t2 = x, i = 0; i < 8; i++) { x2 = self.sampleCurveX(t2) - x; if (Math.abs(x2) < self.epsilon) { return t2; } d2 = self.sampleCurveDerivativeX(t2); if (Math.abs(d2) < 1e-6) { break; } t2 = t2 - x2 / d2; } t0 = 0; t1 = 1; t2 = x; if (t2 < t0) return t0; if (t2 > t1) return t1; while (t0 < t1) { x2 = self.sampleCurveX(t2); if (Math.abs(x2 - x) < self.epsilon) return t2; if (x > x2) t0 = t2; else t1 = t2; t2 = (t1 - t0) * .5 + t0; } // Failure. return t2; }; Select.animate.bezier.prototype.solve = function(x) { return this.sampleCurveY(this.solveCurveX(x)); }; // Defined according to the default css timing functions: https://www.w3.org/TR/css3-transitions/#transition-timing-function Select.animate.bezier.transtionTimingFunctions = { 'ease': [0.25, 0.1, 0.25, 1], 'linear': [0, 0, 1, 1], 'ease-in': [0.42, 0, 1, 1], 'ease-out': [0, 0, 0.58, 1], 'ease-in-out': [0.42, 0, 0.58, 1] }; // tests if the first element matches the selector Select.prototype.matches = function(selector) { return Select.matches(this[0], selector) }; // test if an element matches a selector Select.matches = function(element, selector) { var matches = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.msMatchesSelector || element.oMatchesSelector; return matches.call(element, selector); }; // accepts up to three arguments: event, delegateTarget, callback // if the number of arguments is three, the second argument is the delegateSelector Select.prototype.on = function(event) { var callback, delegateSelector; if (arguments.length === 3) { delegateSelector = arguments[1]; var secondCallback = arguments[2]; callback = function(event) { if (Select.matches(event.target, delegateSelector)) { secondCallback(event); } } } else { callback = arguments[1]; } for (var i = 0, length = this.length; i < length; i++) { Select.on(this[i], event, callback); } }; Select.on = function(element, event, callback) { element.addEventListener(event, function(event) { // use IE's non-standard window.event object when the regular event object is not available event = event || window.event; callback(event); }); }; Select.window = { scrollTop: function(target, options, callback) { Select.extend({ duration: 0, transitionTimingFunction: 'linear' }, options); var bezierCurve = new Select.animate.bezier(options.transitionTimingFunction, options.duration), start = window.pageYOffset, difference = target - start, lastIteration, totalElapsed = 0; var interval = function() { var now = Date.now(), elapsed = lastIteration ? now - lastIteration : 16; lastIteration = now; totalElapsed = Math.min(totalElapsed + elapsed, options.duration); var offsetTop = start + Math.round(difference * bezierCurve.solve(totalElapsed / options.duration)); window.scrollTo(window.pageXOffset, offsetTop); if (target !== offsetTop) { requestAnimationFrame(interval) } else if (callback) { callback(); } } requestAnimationFrame(interval); }, width: function() { return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; }, height: function() { return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight } } // merge two or more simple objects, provided as seperate arguments Select.extend = function() { var base = arguments[0]; for (var i = 1, length = arguments.length; i < length; i++) { var options = arguments[i]; for (name in options) { base[name] = options[name]; } } return base; }; Select.ajax = function(options) { var xhr = new XMLHttpRequest(), options = Select.extend({ type: 'GET', url: location.href, data: null, dataType: 'application/json', success: null, error: null, timeout: 0 }, options); xhr.open(options.type, options.url, true); xhr.setRequestHeader('Content-Type', options.dataType); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { if (options.success) { var data = xhr.responseText; if (options.dataType === 'application/json') { data = JSON.parse(data); } options.success(data); } } else { if (options.error) { options.error(xhr.status, xhr.statusText); } } } }; xhr.timeout = options.timeout; xhr.send(options.data); }; })(); </script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
Select ID Selector
var $el = Select('#hello')
pending…
Select Class Selector
var $el = Select('.hello')
pending…

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL.

0 Comments