Polyfills and Prototypes

written by jonathantneal on June 18, 2012 in JavaScript with 7 comments

Polyfills are a welcome step forward for the www, allowing web developers to write their best code for today’s browsers without sacrificing the experience of those visitors still running the older stuff. A polyfill is code that fills in missing gaps of functionality in a web browser. Browsers that already have the functionality ignore the polyfill. As a result, all browsers get the same functionality, and developers spend less time debugging individual browsers and more time creating great experiences. Think of polyfills as a set of reading glasses for aging browsers.

A polyfill, or polyfiller, is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively. Flattening the API landscape if you will.

Most polyfilling on the web is done through JavaScript prototyping.  In JavaScript, a prototype is an object that other objects inherit, and prototyping refers to the process of modifying the prototype object.  For example, if the String prototype was given a trim function which removed white-space from both ends of a string, then all strings would have this functionality.

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g, '');
};

' Hello World '.trim(); // becomes 'Hello World'

What makes polyfilling unique from prototyping is the intention or expectation of some browsers to handle the functionality without any assistance. Good polyfilling does not overwrite existing functionality.  For example, if a trim function already existed on the String prototype (which, in many browsers, it does), then prototyping would only occur if and when the native functionality was not already present.

!String.prototype.trim && (String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g, '');
});

A good polyfill is also distinguished by its ability to mimic native functionality as closely as possible.  For example, an addEventListener polyfill for IE8 would not only pass events to attachEvent, but it would also mimic the Event object passed into the listener, imitating the target and currentTarget properties, as well as the preventDefault and stopPropagation methods. Good polyfills are meticulous.

// EventListener | @jon_neal | //github.com/jonathantneal/EventListener

!this.addEventListener && this.Element && (function () {
	function addToPrototype(name, method) {
		Window.prototype[name] = HTMLDocument.prototype[name] = Element.prototype[name] = method;
	}

	var registry = [];

	addToPrototype("addEventListener", function (type, listener) {
		var target = this;

		registry.unshift({
			__listener: function (event) {
				event.currentTarget = target;
				event.pageX = event.clientX + document.documentElement.scrollLeft;
				event.pageY = event.clientY + document.documentElement.scrollTop;
				event.preventDefault = function () { event.returnValue = false };
				event.relatedTarget = event.fromElement || null;
				event.stopPropagation = function () { event.cancelBubble = true };
				event.relatedTarget = event.fromElement || null;
				event.target = event.srcElement || target;
				event.timeStamp = +new Date;

				listener.call(target, event);
			},
			listener: listener,
			target: target,
			type: type
		});

		this.attachEvent("on" + type, registry[0].__listener);
	});

	addToPrototype("removeEventListener", function (type, listener) {
		for (var index = 0, length = registry.length; index < length; ++index) {
			if (registry[index].target == this && registry[index].type == type && registry[index].listener == listener) {
				return this.detachEvent("on" + type, registry.splice(index, 1)[0].__listener);
			}
		}
	});

	addToPrototype("dispatchEvent", function (eventObject) {
		try {
			return this.fireEvent("on" + eventObject.type, eventObject);
		} catch (error) {
			for (var index = 0, length = registry.length; index < length; ++index) {
				if (registry[index].target == this && registry[index].type == eventObject.type) {
					registry[index].call(this, eventObject);
				}
			}
		}
	});
})();

To better understand how and when to polyfill JavaScript, surf and study Mozilla’s JavaScript Reference and Kangax’s ECMAScript 5 Compatibility Table, or drink from the firehose at the official ECMAScript website.