Comparison of Event Binding Without a Toolkit and With jQuery

Events are one aspect of the Document Object Model (DOM) that are highly inconsistent across browsers. Back in the early days of the Web, Netscape introduced one model of attaching event listeners to the document. Unsurprisingly, Microsoft chose to implement events in a completely different way in Internet Explorer. When the W3C came along to standardize the process, this only served to muddy the waters even further by introducing a third approach.

Handling Browser Event Inconsistencies

Without a toolkit, the developer is left to work around these inconsistencies themselves. Event handling code without a JavaScript toolkit tends to look something like this:

Code to work around browser incompatibilities when not using a JavaScript toolkit
/*
written by Dean Edwards, 2005
with input from Tino Zijdel - crisp@xs4all.nl
http://dean.edwards.name/weblog/2005/10/add-event/
*/

function addEvent(element, type, handler) {
	if (element.addEventListener) {
		element.addEventListener(type, handler, false);
	} else {
		if (!handler.$$guid) handler.$$guid = addEvent.guid++;
		if (!element.events) element.events = {};
		var handlers = element.events[type];
		if (!handlers) {
			handlers = element.events[type] = {};
			if (element['on' + type]) handlers[0] = element['on' + type];
			element['on' + type] = handleEvent;
		}
	
		handlers[handler.$$guid] = handler;
	}
}

addEvent.guid = 1;

function removeEvent(element, type, handler) {
	if (element.removeEventListener) {
		element.removeEventListener(type, handler, false);
	} else if {(element.events && element.events[type] && handler.$$guid)
		delete element.events[type][handler.$$guid];
	}
}

function handleEvent(event) {
	event = event || fixEvent(window.event);
	var returnValue = true;
	var handlers = this.events[event.type];

	for (var i in handlers) {
		if (!Object.prototype[i]) {
			this.$$handler = handlers[i];
			if (this.$$handler(event) === false) returnValue = false;
		}
	}

	if (this.$$handler) this.$$handler = null;

	return returnValue;
}

function fixEvent(event) {
	event.preventDefault = fixEvent.preventDefault;
	event.stopPropagation = fixEvent.stopPropagation;
	return event;
}

fixEvent.preventDefault = function() {
	this.returnValue = false;
}

fixEvent.stopPropagation = function() {
	this.cancelBubble = true;
}

Binding Events

Assuming we have markup like this...

Example markup
  <ol id="veggies">
    <li>Carrots</li>
    <li>Zucchini</li>
    <li>Squash</li>
  </ol>

...and we want to bind focus handlers to each item in the list, the code using our hand-rolled toolkit would look something like this:

Example of binding event handlers without a JavaScript toolkit
var listOfVeggies = document.getElementById("veggies");
var veggieNodes = listOfVeggies.getElementsByTagName("li");
for (var i = 0; i < veggieNodes.length; i++) {
  addEvent(veggieNodes[i], "onclick", function (evt) { 
    eatVegetable(evt.target); 
  });
}

jQuery provides a simple and succinct mechanism for attaching events to elements. Since we're reusing an existing toolkit, we can get rid of all the event handler workaround code from the first example. The jQuery code to actually bind event handlers to each item in the list looks like this:

Example of binding event handlers with jQuery
jQuery("#veggies > li").focus(function (evt) {
  eatVegetable(evt.target);
});

Just counting the code to actually bind the event handlers, that's a line-for-line savings of nearly 70%, and the latter form is significantly more readable.