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>

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