Wednesday, April 25, 2007

Seven JavaScript Techniques You Should Be Using Today

By Dustin Diaz, Published on April 23, 2007

Whether or not your JavaScript is maintainable often seems to be relative to the task you’re trying to accomplish. You can follow best practices, or not, and sometimes you’ll be fine either way. Nevertheless, in this article, we’ll demonstrate seven techniques you can add to your daily code writing habits to make you a better programmer.

A closure establishes a space in which any variables defined within that space are not accessible from outside that space. This also includes functions themselves, and in fact, it is only functions (changed as of JavaScript 1.7) that can provide block scope and create closures for you.

One thing to keep in mind as you go through these examples is that there is definitely more than one way to accomplish these tasks—the goal here is to shed a little light on how things can be done in a smarter way. The benefits of each method should be self-evident, but, in the end, the purpose is to learn a concise way of identifying common mistakes made by programmers and JavaScript library authors, add to your list of JavaScript ninja skills, and learn the flexibility of the language.
One: Branch when possible

When performance matters, it’s often advisable to branch out your functions in a way that ensures processor-intensive or memory-hungry tasks won’t be frequently repeated. One of the most common scenarios where this situation can arise is handling browser differences. Let’s take a look at the following two examples, and see how we can speed up XHR handling and event listener assignment.

For this first code sample we’ll build an asyncRequest function which puts branching into practice.


var asyncRequest = function() {
function handleReadyState(o, callback) {
var poll = window.setInterval(function() {
if(o && o.readyState == 4) {
window.clearInterval(poll);
if ( callback ){
callback(o);
}
}
},
50);
}
var http;
try {
http = new XMLHttpRequest();
}
catch(e) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for ( var i=0, len = msxml.length; i < len; ++i ) {
try {
http = new ActiveXObject(msxml[i]);
break;
}
catch(e) {}
}
}
return function(method, uri, callback, postData) {
http.open(method, uri, true);
handleReadyState(http, callback);
http.send(postData || null);
return http;
};
}();

Take special note how—through the use of closures—we were able to cleverly branch out our core functionality before a function is returned to the asyncRequest variable. The advantage we gain is that our code will not have to check if the native XMLHttpRequest object is available upon every request, nor will we have to obnoxiously loop through three separate IE cases to check if various versions of the ActiveX compatible component are available. Also, take note how the closure is used to encapsulate our logic (see point Six below) so that we don’t pollute the global namespace with variables that only pertain to getting this particular task done.

Let’s look at another example—one that Dean Edwards touched on about a year ago when he shared a brief tip on how to speed up object detection and apply the same technique of branching we used in our asyncRequest function to the problem of attaching event listeners.

Take note here that this version of addListener will correct the scope of this in your callbacks, as well as send the appropriate event back as the first argument for A-grade browsers. The resulting function, incidentally, will be faster than any entry to the addEvent recoding contest, simply because it puts branching into practice.

var addListener = function() {
if ( window.addEventListener ) {
return function(el, type, fn) {
el.addEventListener(type, fn, false);
};
} else if ( window.attachEvent ) {
return function(el, type, fn) {
var f = function() {
fn.call(el, window.event);
};
el.attachEvent('on'+type, f);
};
} else {
return function(el, type, fn) {
element['on'+type] = fn;
}
}
}();

Two: Make Flags

Making flags is yet another great way to speed up object detection. If you have various functions checking for the same kind of object, then simply create a flag. For instance, if you’re checking to see if you have a browser capable of performing common DOM tasks, it may be wise to set a flag to represent that fact:

var w3 = !!(document.getElementById && document.createElement);

Or, if you’re a browser sniffer, an easy way to check if your visitor is running on Internet Explorer is to check for the ActiveX Object:

var ie = !!window.ActiveX;

The not operators (!!) simply perform a Boolean conversion. The first operator changes the type of the object on the right to a Boolean, and then the second will just reverse whatever the first returned. Nifty trick, eh?

One thing you should pay particular attention to is the scope at which these flags are declared. If your working domain is a small environment such as a blog, with only a few JavaScript bells and whistles, then you should be safe declaring them globally. But if this becomes a larger application (or you just want to keep your code clean), you can namespace your flags into a flags object:

var FLAGS = {
w3: function() {
return !!(document.getElementById && document.createElement);
}(),
ie: function() {
return !!window.ActiveX;
}()
};

The purpose of declaring your flags once is so that you’re not redefining them locally in various functions throughout your application, thus adding logic and code duplication that isn’t necessary.
Three: Make bridges

Be nice to your APIs! A bridge is a design pattern used in software engineering that is created to “decouple an abstraction from its implementation so that the two can vary independently” (Source: Wikipedia). Bridges are your friend when it comes to event-driven programming. If you’re just entering the world of API development—whether it’s a web service API, or a simple getSomething, setSomething API—make bridges to keep them clean.

One of the most practical cases for a bridge is for event listener callbacks. Let’s say you have an API function called getUserNameById. And, naturally, you want this information fetched upon a click event. And of course, the id of the element you click on contains that information. Well, here’s the bad way:

addListener(element, 'click', getUserNameById);
function getUserNameById(e) {
var id = this.id;
// do stuff with 'id'
}

As you can see, we’ve now created a nasty API that depends on a browser implementation that naturally passes back an event object as the first argument, and we only have the scope of callback to work with to grab the id from the this object. Good luck running this from the command line, or running a unit test against it! Instead, why don’t we try the following: Start with the API function first:

function getUserNameById(id) {
// do stuff with 'id'
}

Hey, that looks a little more practical! Now we can program to an interface and not an implementation (sound familiar, pattern junkies?). With that in mind, now we can create a bridge to connect the two functions:

addListener(element, 'click', getUserNameByIdBridge);
function getUserNameByIdBridge (e) {
getUserNameById(this.id);
}

Four: Try Event Delegation

Try is the key word here, since it isn’t practical for all cases, but it’s definitely worth…trying. I first learned of this through chatting with some of the Yahoo! folks and seeing it in action when the Menu widget was re-architected; blogger and Yahoo! developer, Christian Heilmann, has also discussed the topic in his Event Delegation article.

Event delegation is a simple way to cut back on event attachment. It works by adding a listener to a container element, and then retrieving the target that fired the event, rather than attaching several listeners to the children and accessing the element object through the this object. To give a simple example: If you have a ul (unordered list) with fifty list items as its children, and you want to handle the click event of those lis, it is more efficient to just assign one click event to the entire ul and then handle the click’s target. Here’s an example:


  • foo

  • bar

  • baz

  • thunk


var element = document.getElementById('example');
addListener(element, 'click', handleClick);
function handleClick(e) {
var element = e.target || e.srcElement;
// do stuff with 'element'
}

In this example, when a user clicks on an li element, it is essentially the same as clicking on the ul element. We can track down the source target by inspecting the event object that was invisibly passed to the callback method—in this case e (e is a common variable naming convention used for event). e contains a property called target (which is the target element), or in Internet Explorer, the property is called srcElement. We simply use a logical OR operator to determine if the target property can be found; if not, we default to srcElement for IE.
Five: Include methods with your getElementsByWhatever

One of the most notable omissions in nearly all JavaScript library APIs is the lack of an ability to add callbacks to element collector functions. Whether it’s a getElementsByClassName, getElementsByAttribute, or even a CSS querying engine, think about the logic behind what you want to do with these collections in the end; it just makes sense to allow the ability to have callbacks.

Step back for one moment and think about what goes on behind the scenes every time you want to get an element collection: You usually want to do something with it. And if your getElementsByWhatever function only allows you to get an array of elements and return them, don’t you think it’s a bit of a waste to iterate through that entire collection again when you’re ready to attach a function to the elements?

Let’s start off with a standard selector API that allows you to get elements in the DOM based on a CSS selector:

function getElementsBySelector(selector) {
var elements = [];
for ( ... ) {
if ( ... ) {
elements.push(el);
}
}
return elements;
}

Now that we have an array of matching elements, we want to attach some functionality to them:

var collection = getElementsBySelector('#example p a');
for ( var i = collection.length - 1; i >=0; --i ) {
// do stuff with collection[i]
}

By the end of this process, we’ve run two loops for what can be done in one. With that in mind, we can change our function to register the function callback as part of the initial loop through the matching elements:

function getElementsBySelector(selector, callback) {
var elements = [];
for ( ... ) {
if ( ... ) {
elements.push(el);
callback(el);
}
}
return elements;
}

getElementsBySelector('#example p a', function(el) {
// do stuff with 'el'
});

Six: Encapsulate your code

This is an oldie, but still rarely practiced. When you’re implementing JavaScript that starts to become large and unmanageable (trust me, it happens to us all), the most reliable safeguard to protect your code and let it play nicely with the other JavaScript kids is to simply use a closure. Instead of having a page full of globals:

var a = 'foo';
var b = function() {
// do stuff...
};
var c = {
thunk: function() {

},
baz: [false, true, false]
};

We can add a closure:

(function() {
var a = 'foo';
var b = function() {
// do stuff...
};
var c = {
thunk: function() {

},
baz: [false, true, false]
};
})();

The reason this safeguards your code is that closures give your code a closing scope that does not allow predators (or other messy engineers) to mess with it. A closure establishes a space in which any variables defined within that space are not accessible from outside that space. This also includes functions themselves, and in fact, it is only functions (changed as of JavaScript 1.7) that can provide block scope and create closures for you. Try the following example to see how the scope chain travels from the innermost function, outward.

// global land has access to ONLY a, but cannot execute code within it
function a() {
// has access to a, and b, but cannot execute code within b, c, and d
function b() {
// has access to a, b, and c, but cannot execute code within c, and d
function c() {
// has access to a, b, c, and d but cannot execute code within d
function d() {
// has access to all, and the ability to execute all
}
}
}
}

Seven: Reinvent the wheel

Last, but not least, reinvent the wheel. Okay, this isn’t a clever technique or anything to do with code—rather, it is a note of encouragement to never feel ashamed to try something radical when writing JavaScript.

Often, when I sit down to prototype out code or build a widget, I’m almost positive someone has already accomplished what I’m about to attempt. But nevertheless, it’s worth my effort to see if I can do it better. Not every wheel is round. Nor does every round wheel have twenty-inch shiny spinners. Imagination with such a highly flexible language can take you a long way.

No comments :