JavaScript Archive

Notes From A Selector Engine

As previously stated, I’ve recently been working on a selector engine. Unfortunately, as every browser behaves slightly differently, I’ve had to resort to constructing compatibility tables. Seeing as they can be useful in other applications, I thought I’d post it here:

Element.querySelectorAll

A browser-native selector engine that allows you to select element nodes based on a CSS selector (for example: div p a).

var a = document.getElementById('a');

a.querySelectorAll('div.post > p a[class]');
IE6 IE7 IE8 Firefox 3 Firefox 3.5 Opera 9 Opera 10 Safari 3 Safari 4 Chrome 1 Chrome 2
no no no no yes no yes no yes yes yes

Element.firstElementChild / Element.lastElementChild / Element.previousElementSibling / Element.nextElementSibling

Selects the first/last element child or the previous/next element sibling. Saves on extrenuous code like:

var previous = document.getElementById('example');
while((previous = previous.previousSibling) !== null && previous.nodeType !== 1) { }
var a = document.getElementById('a');

a.firstElementChild;
a.lastElementChild;
a.previousElementSibling;
a.nextElementSibling;
IE6 IE7 IE8 Firefox 3 Firefox 3.5 Opera 9 Opera 10 Safari 3 Safari 4 Chrome 1 Chrome 2
no no no no yes yes yes yes yes yes yes

Element.children

Similar to the childNodes method, but without text nodes and comments.

var a = document.getElementById('a');

a.children;
IE6 IE7 IE8 Firefox 3 Firefox 3.5 Opera 9 Opera 10 Safari 3 Safari 4 Chrome 1 Chrome 2
almost (includes comments) almost (includes comments) almost (includes comments) no yes yes yes yes yes yes yes

Element.compareDocumentPosition

Allows you to compare one node’s position to another. Returns a bitmask.

var a = document.getElementById('a');
var b = document.getElementById('b');

a.compareDocumentPosition(b);
IE6 IE7 IE8 Firefox 3 Firefox 3.5 Opera 9 Opera 10 Safari 3 Safari 4 Chrome 1 Chrome 2
no no no yes yes yes yes yes yes no yes

Element.contains

Returns a boolean whether or not an element is contained within another.

var a = document.getElementById('a');
var b = document.getElementById('b');

a.contains(b);
IE6 IE7 IE8 Firefox 3 Firefox 3.5 Opera 9 Opera 10 Safari 3 Safari 4 Chrome 1 Chrome 2
yes yes yes no no yes yes yes yes yes yes

Array Tidy

I’ve been working on a selector engine recently, and given the complexity of it, it’s easier to return a multi-dimensional array than a flat one (for example: [['this'], 'is', ['an', 'example']] instead of ['this', 'is', 'an', 'example']). Of course, returning such an array to another function is a bit pointless because it requires that function to traverse through the array in order to flatten it so it can be used efficiently. Another problem with the selector engine is that sometimes, an element can be returned twice (for example: $('div, #content'), where #content is a div). Because of this, I wrote a tidy function that flattens the array and removes duplicate indexes.

Unfortunately, it requires some additional code:

indexOf

Returns the first index where a value (searchStr) appears. startIndex allows you to begin the search at a predefined index.

Array.prototype.indexOf = Array.prototype.indexOf || function(searchStr, startIndex) {
	for(var i = startIndex || 0, thisLength = this.length; i < thisLength; i++) {
		if(searchStr === this[i]) {
			return i;
		}
	}

	return -1;
};

inArray

Returns either true or false if a value (searchStr) is in an array. startIndex allows you to begin the search at a predefined index.

Array.prototype.inArray = function(searchStr, startIndex) {
	return this.indexOf(searchStr, startIndex) > -1;
};

flatten

Flattens a multi-dimensional array to a one-dimensional array.

Array.prototype.flatten = function() {
	for(var i = 0, thisLength = this.length, b = []; i < thisLength; i++) {
		if(this[i].constructor === Array) {
			this[i] = this[i].flatten();

			for(var j = 0, thisILength = this[i].length; j < thisILength; j++) {
				b.push(this[i][j].constructor === Array ? this[i][j].flatten() : this[i][j]);
			}
		}
		else {
			b.push(this[i]);
		}
	}

	return b;
};

tidy

Tidies up an array by removing dupe values and flattening it to a one dimensional array.

Array.prototype.tidy = function() {
	var a = this.flatten();
	var b = [];

	for(var i = 0, aLength = a.length; i < aLength; i++) {
		if(!b.inArray(a[i])) {
			b.push(a[i]);
		}
	}

	return b;
};

All this code has been tested on the following browsers and works perfectly:

  • Firefox 1.5, 2, 3, 3.5
  • Chrome 1, 2, 3
  • Opera 7, 8, 9.5, 9.64, 10
  • Safari 3, 4
  • Internet Explorer 6, 7, 8

JavaScript Benchmarking

I’ve been coding a few functions and methods for an upcoming project and needed to benchmark them. Firebug’s profile tool is pretty good, but it only takes into account one execution of the code – but what if that one instance was particularly slow or particularly fast? For better results, you’d have to repeat the function multiple times to get a better idea of how quick (or slow) it is. So in order to do this, I’ve built a benchmarking tool that bolts onto the Function Prototype:

Function.prototype.benchmark = function(iterations) {
	if(iterations === null || isNaN(iterations) || iterations < 1) { throw new TypeError(); }

	var begin = (new Date()).getTime();

	for(var i = 0; i < iterations; i++) {
		this();
	}

	var end = (new Date()).getTime();

	return (end - begin) / iterations; // in milliseconds
};

Use it like this:

var example = function() {
	// run some code
};

var benchmark = example.benchmark(100); // run example 100 times

In a nutshell, example is executed 100 times and benchmark contains the average time it took in milliseconds to execute it. Obviously more iterations gives a better average but could potentially crash if the number is too high. For big functions and libraries, I’d recommend starting off low (maybe 100) and then ramping it up but for small functions, you could easily start at up to 10,000 or maybe even 100,000. Make sure there isn’t any code in there that could potentially ruin the benchmarking – particularly alerts.

Remember to take into account slow machines and portable devices, as not every browser and computer will do it at the same speed.

Image Preloader

With Ajax being all the rage, preloading images has become somewhat important. The problem is, loading them with all the other page components adds to the load time and it may not even be used. Enter preloadImage – a function set to stop all that. It’s very simple but can be used to powerful means:

var preloadImage = function(url, callback) {
	var image = new Image();
	image.src = url;
	image.onload = image.onabort = image.onerror = callback;
	return image;
};

Just specify the URL (url) and the callback function (callback) as so:

var callback = function(e) {
	alert(e.type);
};

var image = preloadImage('http://www.sidroberts.co.uk/image.gif', callback);

On the load event, the abort event or the error event, the callback function will be executed. You can distinguish these with the type property which is "load", "abort" and "error" respectively.

Now any image that points to that URL will load almost instantly.

Extending Regular Expressions

You know the problem with regular expressions in JavaScript?: There’s not enough to do. Sure, you can use its methods against strings but there’s nothing that allows you to manipulate the actual expressions. Where is the property for all the modifiers? How can you add or remove a modifier? The simple answer is that it isn’t possible. The long and complicated way says that it is. The only problem is that the code is a little bit ugly:

RegExp.prototype.modifiers = function() {
	return /^\/(.+)\/([gim]+?)$/i.exec(this.toString())[2].split('');
};

Don’t say I didn’t warn you.

In short, that method converts the expression to a string and then executes another regular expression with it. This new regular expression takes away the main pattern and leaves the modifiers in a string. That string is then split into individual characters so you have an array of them (for example: ['g', 'i']).

This magic regular expression can also be used for extracting a pattern, which, in turn, allows you to rebuild it with the RegExp constructor – modifiers included. This is the method for extracting the pattern:

RegExp.prototype.pattern = function() {
	return /^\/(.+)\/([gim]+?)$/i.exec(this.toString())[1];
};

Rebuilding it is as simple as this:

var original = /\s+/g;

var pattern = original.pattern();
var modifiers = original.modifiers().join('');

var better = new RegExp(pattern, modifiers);

So, putting two and two together, if we can extract the pattern and the modifiers, and rebuild it, what’s stopping us from changing it along the way? Enter addModifiers and removeModifiers:

RegExp.prototype.addModifiers = function(modifiers) {
	return new RegExp(this.pattern(), this.modifiers().join('') + modifiers.join(''));
};

RegExp.prototype.removeModifiers = function(modifiers) {
	return new RegExp(this.pattern(), this.modifiers().join('').replace(new RegExp(modifiers.join('|'), 'i'), ''));
};

With the addModifiers method, you may have noticed that I haven’t bothered to check for the existing modifiers. This is because you can duplicate a modifier on a regular expression and it won’t matter as it’ll only be counted once. Both of these methods allow multiple modifiers and require them to be an array (['g', 'i']):

var original = /\s+/g;

original = original.addModifiers(['i']); // /\s+/gi
original = original.removeModifiers(['g']); // /\s+/i

pattern = original.pattern(); // '\s+'
modifiers = original.modifiers(); ['i']