JavaScript Archive

Notes From A Selector Engine

As pre­vi­ously stated, I’ve recently been work­ing on a selector engine. Unfor­tu­nately, as every browser behaves slightly dif­fer­ently, I’ve had to resort to con­struct­ing com­pat­ib­il­ity tables. See­ing as they can be use­ful in other applic­a­tions, I thought I’d post it here:

Element.querySelectorAll

A browser-native selector engine that allows you to select ele­ment 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 Fire­fox 3 Fire­fox 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 ele­ment child or the previous/next ele­ment sib­ling. Saves on extrenu­ous 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 Fire­fox 3 Fire­fox 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

Sim­ilar to the childNodes method, but without text nodes and comments.

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

a.children;
IE6 IE7 IE8 Fire­fox 3 Fire­fox 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 com­pare one node’s pos­i­tion to another. Returns a bit­mask.

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

a.compareDocumentPosition(b);
IE6 IE7 IE8 Fire­fox 3 Fire­fox 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 ele­ment is con­tained within another.

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

a.contains(b);
IE6 IE7 IE8 Fire­fox 3 Fire­fox 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 work­ing on a selector engine recently, and given the com­plex­ity 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, return­ing such an array to another func­tion is a bit point­less because it requires that func­tion to tra­verse through the array in order to flat­ten it so it can be used effi­ciently. Another prob­lem with the selector engine is that some­times, an ele­ment can be returned twice (for example: $('div, #content'), where #content is a div). Because of this, I wrote a tidy func­tion that flat­tens the array and removes duplic­ate indexes.

Unfor­tu­nately, it requires some addi­tional code:

indexOf

Returns the first index where a value (searchStr) appears. startIndex allows you to begin the search at a pre­defined 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 pre­defined index.

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

flatten

Flat­tens 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 remov­ing dupe val­ues and flat­ten­ing it to a one dimen­sional 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 fol­low­ing browsers and works perfectly:

  • Fire­fox 1.5, 2, 3, 3.5
  • Chrome 1, 2, 3
  • Opera 7, 8, 9.5, 9.64, 10
  • Safari 3, 4
  • Inter­net Explorer 6, 7, 8

JavaScript Benchmarking

I’ve been cod­ing a few func­tions and meth­ods for an upcom­ing pro­ject and needed to bench­mark them. Firebug’s pro­file tool is pretty good, but it only takes into account one exe­cu­tion of the code — but what if that one instance was par­tic­u­larly slow or par­tic­u­larly fast? For bet­ter res­ults, you’d have to repeat the func­tion mul­tiple times to get a bet­ter idea of how quick (or slow) it is. So in order to do this, I’ve built a bench­mark­ing 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 nut­shell, example is executed 100 times and benchmark con­tains the aver­age time it took in mil­li­seconds to execute it. Obvi­ously more iter­a­tions gives a bet­ter aver­age but could poten­tially crash if the num­ber is too high. For big func­tions and lib­rar­ies, I’d recom­mend start­ing off low (maybe 100) and then ramp­ing it up but for small func­tions, you could eas­ily start at up to 10,000 or maybe even 100,000. Make sure there isn’t any code in there that could poten­tially ruin the bench­mark­ing — par­tic­u­larly alerts.

Remem­ber to take into account slow machines and port­able devices, as not every browser and com­puter will do it at the same speed.

Image Preloader

With Ajax being all the rage, pre­load­ing images has become some­what import­ant. The prob­lem is, load­ing them with all the other page com­pon­ents adds to the load time and it may not even be used. Enter preloadImage — a func­tion set to stop all that. It’s very simple but can be used to power­ful means:

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

Just spe­cify the URL (url) and the call­back func­tion (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 call­back func­tion will be executed. You can dis­tin­guish these with the type prop­erty 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 prob­lem with reg­u­lar expres­sions in JavaS­cript?: There’s not enough to do. Sure, you can use its meth­ods against strings but there’s noth­ing that allows you to manip­u­late the actual expres­sions. Where is the prop­erty for all the mod­i­fi­ers? How can you add or remove a mod­i­fier? The simple answer is that it isn’t pos­sible. The long and com­plic­ated way says that it is. The only prob­lem 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 con­verts the expres­sion to a string and then executes another reg­u­lar expres­sion with it. This new reg­u­lar expres­sion takes away the main pat­tern and leaves the mod­i­fi­ers in a string. That string is then split into indi­vidual char­ac­ters so you have an array of them (for example: ['g', 'i']).

This magic reg­u­lar expres­sion can also be used for extract­ing a pat­tern, which, in turn, allows you to rebuild it with the RegExp con­structor — mod­i­fi­ers included. This is the method for extract­ing the pattern:

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

Rebuild­ing 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, put­ting two and two together, if we can extract the pat­tern and the mod­i­fi­ers, and rebuild it, what’s stop­ping us from chan­ging 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 exist­ing mod­i­fi­ers. This is because you can duplic­ate a mod­i­fier on a reg­u­lar expres­sion and it won’t mat­ter as it’ll only be coun­ted once. Both of these meth­ods allow mul­tiple mod­i­fi­ers 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']