Writing Responsible JavaScript
Without a doubt, JavaScript has been making something of a comeback in the last year. If you’re involved in client-side development in any way at all, chances are that you’re finding yourself writing more JavaScript now than you have in a long time.
If you learned most of your JavaScript back when DHTML was all the rage and before DOM Scripting was in vogue, there have been some big shifts in the way scripts are written. Most of these are in the way event handlers are assigned and functions declared. Both of these changes are driven by the desire to write scripts that are responsible page citizens, both in not tying behaviour to content and in taking care not to conflict with other scripts. I thought it may be useful to look at some of these more responsible approaches to learn how to best write scripts that are independent of the page content and are safely portable between different applications.
Event Handling
Back in the heady days of Web 1.0, if you wanted to have an object on the page react to something like a click, you would simply go ahead and attach an onclick
attribute. This was easy and understandable, but much like the font
tag or the style
attribute, it has the downside of mixing behaviour or presentation in with our content. As we’re learned with CSS, there are big benefits in keeping those layers separate. Hey, if it works for CSS, it should work for JavaScript too.
Just like with CSS, instead of adding an attribute to our element within the document, the more responsible way to do that is to look for the item from your script (like CSS does with a selector) and then assign the behaviour to it. To give an example, take this oldskool onclick
use case:
<a id="anim-link" href="#" onclick="playAnimation()">Play the animation</a>
This could be rewritten by removing the onclick
attribute, and instead doing the following from within your JavaScript.
document.getElementById('anim-link').onclick = playAnimation;
It’s all in the timing
Of course, it’s never quite that easy. To be able to attach that onclick
, the element you’re targeting has to exist in the page, and the page has to have finished loading for the DOM to be available. This is where the onload
event is handy, as it fires once everything has finished loading. Common practise is to have a function called something like init()
(short for initialise) that sets up all these event handlers as soon as the page is ready.
Back in the day we would have used the onload
attibute on the <body>
element to do this, but of course what we really want is:
window.onload = init;
As an interesting side note, we’re using init
here rather than init()
so that the function is assigned to the event. If we used the parentheses, the init
function would have been run at that moment, and the result of running the function (rather than the function itself) would be assigned to the event. Subtle, but important.
As is becoming apparent, nothing is ever simple, and we can’t just go around assigning our initialisation function to window.onload
. What if we’re using other scripts in the page that might also want to listen out for that event? Whichever script got there last would overwrite everything that came before it. To manage this, we need a script that checks for any existing event handlers, and adds the new handler to it. Most of the JavaScript libraries have their own systems for doing this. If you’re not using a library, Simon Willison has a good stand-alone example
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
Obviously this is just a toe in the events model’s complex waters. Some good further reading is PPK’s Introduction to Events.
Carving out your own space
Another problem that rears its ugly head when combining multiple scripts on a single page is that of making sure that the scripts don’t conflict. One big part of that is ensuring that no two scripts are trying to create functions or variables with the same names. Reusing a name in JavaScript just over-writes whatever was there before it.
When you create a function in JavaScript, you’ll be familiar with doing something like this.
function foo() {
... goodness ...
}
This is actually just creating a variable called foo
and assigning a function to it. It’s essentially the same as the following.
var foo = function() {
... goodness ...
}
This name foo
is by default created in what’s known as the ‘global namespace’ – the general pool of variables within the page. You can quickly see that if two scripts use foo
as a name, they will conflict because they’re both creating those variables in the global namespace.
A good solution to this problem is to add just one name into the global namespace, make that one item either a function or an object, and then add everything else you need inside that. This takes advantage of JavaScript’s variable scoping to contain you mess and stop it interfering with anyone else.
Creating An Object
Say I was wanting to write a bunch of functions specifically for using on a site called ‘Foo Online’. I’d want to create my own object with a name I think is likely to be unique to me.
var FOOONLINE = {};
We can then start assigning functions are variables to it like so:
FOOONLINE.message = 'Merry Christmas!';
FOOONLINE.showMessage = function() {
alert(this.message);
};
Calling FOOONLINE.showMessage()
in this example would alert out our seasonal greeting. The exact same thing could also be expressed in the following way, using the object literal syntax.
var FOOONLINE = {
message: 'Merry Christmas!',
showMessage: function() {
alert(this.message);
}
};
Creating A Function to Create An Object
We can extend this idea bit further by using a function that we run in place to return an object. The end result is the same, but this time we can use closures to give us something like private methods and properties of our object.
var FOOONLINE = function(){
var message = 'Merry Christmas!';
return {
showMessage: function(){
alert(message);
}
}
}();
There are two important things to note here. The first is the parentheses at the end of line 10. Just as we saw earlier, this runs the function in place and causes its result to be assigned. In this case the result of our function is the object that is returned at line 4.
The second important thing to note is the use of the var
keyword on line 2. This ensures that the message
variable is created inside the scope of the function and not in the global namespace. Because of the way closure works (which if you’re not familiar with, just suspend your disbelief for a moment) that message
variable is visible to everything inside the function but not outside. Trying to read FOOONLINE.message
from the page would return undefined
.
This is useful for simulating the concept of private class methods and properties that exist in other programming languages. I like to take the approach of making everything private unless I know it’s going to be needed from outside, as it makes the interface into your code a lot clearer for someone else to read.
All Change, Please
So that was just a whistle-stop tour of a couple of the bigger changes that can help to make your scripts better page citizens. I hope it makes useful Sunday reading, but obviously this is only the tip of the iceberg when it comes to designing modular, reusable code.
For some, this is all familiar ground already. If that’s the case, I encourage you to perhaps submit a comment with any useful resources you’ve found that might help others get up to speed. Ultimately it’s in all of our interests to make sure that all our JavaScript interoperates well – share your tips.
About the author
Drew McLellan is a developer and content management consultant from Bristol, England. He’s the lead developer for the popular Perch and Perch Runway content management systems, and public speaking portfolio site Notist. Drew was formerly Group Lead at the Web Standards Project, and a Search Innovation engineer at Yahoo!. When not publishing 24 ways, he keeps a personal site about web development, takes photos, tweets a lot and tries to stay upright on his bicycle.