“Brief” Guide to JavaScript – Part 9
Finally, another part. If you haven’t already, check out the previous parts of this series:
Trying and Catching
Tries & catches are identical to exceptions in other programming languages. If you're not familiar with exceptions & handling them, let's review now. In general purpose programming, errors (aka, exceptions) are thrown for unreliable operations: the network may drop, insufficient permissions, a file is missing, etc. The programming language will generate these exceptions for us, but we can also create our own exceptions if need be.
When an exception is thrown, it halts the executing code and bubbles up out of code blocks (for loops, while loops, if statements, functions, etc.) until it reaches a try block. If caught by a try block, the subsequent catch block is executed. If no try block exists, the exception terminates the execution of the program/script entirely.
In a nutshell, try/catch blocks allow us to handle code that may potentially fail. This is made more useful in many dynamic languages, where exceptions are thrown from almost any error: from stack overflows to accessing a non-existent variable.
But in JavaScript, exceptions are less useful since most of JavaScript relies on generated content: the DOM tree and browser-provided functions. Nonetheless, some browser functions will throw exceptions if they fail. Raised exceptions that are not in a try/catch block are handled by the browser (usually by being display to the JavaScript debugger/console) and the script halts execution.
Let's see some concrete examples:
// result: this code will always show the alert (since we always throw an exception)
// but normally it isn't guaranteed that exceptions are thrown.
try {
throw "Oh noes!";
}
catch(error){ // error contains "Oh noes!"
alert(error);
}
Here we simply throw our own exception in our try/catch block. Throw accepts any data to pass to a catch block. Unlike custom exceptions, language-based exceptions' data varies from browser to browser and shouldn't be relied upon.
DOM Ready Event
After using the load event you may have noticed that your JavaScript executes only after everything finishes loaded (which includes images, flash, ads, etc.). We may have a 200-megabyte image on the page (just pretend at least), the JavaScript won't run until that image is finished downloaded by the browser: Not ideal.
We can solve this using the DOM Ready Event. But we should talk a bit about the aforementioned solution I put earlier: simply placing your script tags at the bottom.
The browser creates the DOM tree as the HTML is parsed by the browser. This means any JavaScript code is executed as soon as it is reached by the browser, and giving access to the partially-completed DOM tree it has built. If we place our scripts at the bottom of the page, the entire DOM tree has been built before our scripts execute: which is perfect.
Putting scripts at the bottom also provides a nice side-effect as mentioned on Yahoo!. They speed up loading of your pages and allows the browser to render the page as it parses it (think of how a stereotypical page loads on 56k). This is opposed to freezing other downloading files and providing a blank page until the entire page downloaded and completely parsed when you place script tags in your head.
But sometimes it's useful the place script in head, they don't interfere with your DOM tree manipulations (most of the time) if they're in head as opposed to body. Also, since the page isn't displayed yet, you can hide an element (in conjunction with DOM Ready) before it's displayed. If you placed the script at the bottom, the user could be trying to interact with some elements you didn't want visible while the page is loaded since the browser didn't get to the scripts which hides the element.
For DOM Ready, most browsers support the DOMContentLoaded event which works as intended. But the pesky IE needs an alternative solution, via readystatechange, which can report when the status of an object changes, like when it finishes loading.
But the timing of readystatechange isn't reliable, it can also occur after load. A better method constantly try to use certain functions that IE only provides when the DOM tree is ready.
And here's the monstrosity, commented for understandability.
var isReady = false;
// list of all functions that should be called when DOMReady is achieved.
var readyFunctions = [];
// this is called when the DOM is ready and all functions that
// want to execute when the DOM is ready (stored in
// readyFunctions) are called.
function ready(){
// this will be called multiple times during a load, but we only
// want its effects once
if(!isReady){
isReady = true;
for(var i=0; i<readyFunctions.length; i++){
readyFunctions[i]();
}
}
}
// attach our event listeners for the DOM Ready event
if(document.addEventListener){
// covers all cases (frames & non-frames) for FF2+, Safari 3+, Chrome,
// Opera 9+
document.addEventListener('DOMContentLoaded', function(){
document.removeEventListener('DOMContentLoaded',
arguments.callee, false);
ready();
}, false);
} else if (document.attachEvent){ // good old internet explorer
// works well with frames for IE, but sometimes maybe called *after*
// the load event.
document.attachEvent('onreadystatechange', function(){
// check it's the right state we're looking for
if(document.readyState === 'complete'){
document.detachEvent('onreadystatechange',
arguments.callee);
ready();
}
});
// do this only if IE (by checking for element.doScroll) and
// is not a frame
if(document.documentElement.doScroll && window == window.top){
// this is a recursive anonymous function that
// is executed immediately
(function(){
// if load got called before we detected it, terminate
if(isReady) return;
try {
// try to use doScroll, which throws an exception
// if DOM tree isn't ready yet.
document.documentElement.doScroll('left');
} catch (error){
// catch the exception and try again (recursive
// call to anonymous function again)
setTimeout(arguments.callee, 0);
// ensures we don't call ready() below when the DOM
// tree isn't really ready.
return;
}
ready(); // we didn't get an exception, fire ready function.
})();
}
}
// if all else fails. For simplicity, the onload property
// is used instead of attachEvent/addEventListener.
window.onload = ready;
Note: I'm basing my example code from code from jQuery, which also utilizes the IE trick documented here.
Theoretically, you could tidy the code a bit better, but it's usable. This should be before all your regular code. To make some code execute immediately after the DOM tree is loaded simply added your functions to the readyFunctions array:
readyFunctions.push(function(){
// usually DOM manipulation or attaching events go here.
alert('dom is ready!');
});
In this case, the alert box will be displayed when the DOM tree is finished, before all the images and flash are loaded.
Namespaces
Let's assume we have two JavaScript developers: Jim and Sammy. Jim writes a function addItem, which handles adding a new item to the inventory. Sammy writes a function, also called addItem, which handles adding a new item to a customer's shopping cart. There's a conflict since a program won't know the difference between addItem that Jimmy wrote versus Sammy.
Namespaces is a solution to this problem by essentially prefixing a set of variables & functions.
In JavaScript, namespaces are simply global objects that contain your functions and variables.
var hui = {
alert_value: 'hello world!',
createElement: function(){
var div = document.createElement('div');
document.body.appendChild(div);
alert(hui.alert_value);
}
};
// call through our namespace:
hui.createElement();
But this can be annoying since we have to refer to the namespace whenever we want to use a function or variable. In addition, any inter-function variables are publicly visible. A better alternative is to wrap everything in an anonymous function:
(function(){
var alert_value = 'hello world!';
function createElement(){
var div = document.createElement('div');
document.body.appendChild(div);
alert(alert_value); // local variable
};
window.hui = {
alert_value: alert_value,
createElement: createElement
};
})();
// call through our namespace:
hui.createElement();
Like any regular functions, the anonymous function here provides its own scope which hides any local variables. After we have defined our functions and variables, we can then create an object in the window object (the global namespace) with the exact functions and variables we want publicly available.
While you may not use namespaces when writing for a specific page, this is definitely more useful when writing your own framework or portable/reusable code.
Yes, I’ve been slacking (but coursework is a good excuse! Honest!) Besides this, I was thinking of migrating towards screencasts (screenr / jing) instead of just text-based tutorials. I feel those would be easier to churn out, in theory.
Next up: Objects Revisited.