Chapter 4. Variables, Functions, and Flow Control

Introduction

This chapter covers a miscellany of core JavaScript topics. A couple of these recipes (or your own variations on them) may be part of your daily menu. If you don’t use these constructions frequently, let this chapter serve to refresh your memory, and give you models to get you back on track when you need them.

Even simple subjects, such as JavaScript variables and functions, have numerous nuances that are easy to forget over time. A couple of concepts, such as exception handling and the try/catch construction, are also comparatively new in recent browsers. Scripters without formal programming training tend to be rather loose in their attention to detail in the error department—something that can come back to bite you. On the other hand, the browser implementations of some of the details of exception handling are far from compatible. If you aren’t yet using exception-handling techniques in your scripts (perhaps the needs of backward-compatibility prevent it), you should still get to know the concepts. As time goes on and the full W3C DOM becomes implemented in browsers, the notion of “safe scripting” will include regular application of exception-handling practices.

This chapter ends with some suggestions about improving script performance. Most scripts can scrape by with inefficiencies, but larger projects that deal with complex document trees and substantial amounts of hidden data delivered to the client must pay particular attention to performance. You’ll learn some practices here that you should apply even to short scripts.

Creating a JavaScript Variable

NN 2, IE 3

Problem

You want to create a JavaScript variable value either in the global space or privately within a function.

Solution

Use the var keyword to define the first instance of every variable, whether you assign a value to the variable immediately or delay the assignment until later. Any variable defined outside of a function is part of the global variable scope:

var myVar = someValue;

All script statements on the page, including those inside functions, have read/write access to a global variable.

When you define a variable with var inside a function, only statements inside the function can access that variable:

function myFunction( ) {
    var myFuncVar = someValue;
    ...
}

Statements outside of the function cannot reach the value of a variable whose scope is limited to its containing function.

Discussion

A JavaScript variable has no inherent limit to the amount of data it can hold. Maximum capacity is determined strictly by memory available to the browser application—information not accessible to your scripts.

Variable scope is an important concept to understand in JavaScript. Not only is a global variable accessible by all script statements in the current window or frame, but statements in other frames or windows (served from the same domain and server) can access those global variables by way of the window or frame reference. For example, a statement in a menu frame can reference a global variable named myVar in a frame named content as follows:

parent.content.myVar

You don’t have to worry about the same global variable names colliding when they exist in other windows or frames, because the references to those variables will always be different.

Where you must exercise care is in defining a new variable inside a function with the var keyword. If you fail to use the keyword inside the function, the variable is treated as a global variable. If you have defined a global variable with the same name, the function’s assignment statement overwrites the value originally assigned to the global variable. The safest way to avoid these kinds of problems is to always use the var keyword with the first instance of any variable, regardless of where it’s located in your scripts. Even though the keyword is optional for global variable declarations, it is good coding style to use var for globals as well. That way you can readily see where a variable is first used in a script.

Although some programming languages distinguish between the tasks of declaring a variable (essentially reserving memory space for its value) and initializing a variable (stuffing a value into it), JavaScript’s dynamic memory allocation for variable values unburdens the scripter of memory concerns. A variable is truly variable in JavaScript in that not only can the value stored in the variable change with later reassignments of values, but even the data type of the variable’s value can change (not that this is necessarily good programming practice, but that’s simply a by-product of JavaScript’s loose data typing).

Speaking of good programming practice, it is generally advisable to define global variables near the top of the script, just as it’s also advisable to define heavily used variables inside a function at the top of the function. Even if you don’t have a value ready to assign to the variable, you can simply declare the variable as undefined with a statement like the following:

var myVar;

If you have multiple variables that you’d like to declare, you may do so compactly by separating the variable names with commas:

var myVar, counter, fred, i, j;

You may even combine declarations and initializations in a comma-delimited statement:

var myVar, counter = 0, fred, i, j;

In examples throughout this book, you typically find variables being declared or initialized at the top of their scope regions, but not always. It’s not unusual to find variables that are about to be used inside a for loop defined (with their var keywords) just before the loop statements. For example, if a nested pair of loops is in the offing, I may define the loop counter variables prior to the outer loop’s start:

var i, j;
for (i = 0; i < array1.length; i++) {
    for (j = 0; j < array1[i].array2.length; j++) {
        ...
    }
}

This is merely my style preference. But in any case, this situation definitely calls for declaring the variables outside of the loops for another reason. If you were to use the var keywords in the loop counter initialization statements (e.g., var j = 0;), the nested loop would repeatedly invoke the var declaration keyword each time the outer loop executes. Internally, the JavaScript interpreter creates a new variable space for each var keyword. Fortunately, the interpreter is also able to keep track of which variable repeatedly declared is the current one, but it places an unnecessary burden on resources. Declare once, then initialize and reassign values as often as needed. Thus, in complex functions that have two or more outer for loops, you should declare the counter variable at the top of the function, and simply initialize the value at the start of each loop.

As for selecting the names for your variables, there are some explicit rules and implicit customs to follow. The explicit rules are more important. A variable name cannot:

  • Begin with a numeral

  • Contain any spaces or other whitespace characters

  • Contain punctuation or symbols except the underscore character

  • Be surrounded by quote marks

  • Be a reserved ECMAScript keyword (see Appendix C)

Conventions among programmers with respect to devising names for variables are not rigid, nor do they affect the operation of your scripts. They do, however, help in readability and maintenance when it comes time to remember what your script does six months from now.

The main idea behind a variable name is to help you identify what kind of value the variable contains (in fact, names are commonly called identifiers). Littering your scripts with a bunch of one- or two-letter variables won’t help you track values or logic when reading the script. On the other hand, there are performance reasons (see Recipe 4.8) to keep names from getting outrageously long. The shorter the better, but not to the point of cryptic ciphers. If you need two or more words to describe the value, join the words together via underscore characters, or capitalize the first character of any words after the first word (a convention used throughout this book). Thus, either of the variable names in the following initializations are fine:

var teamMember = "George";
var team_member = "George";

Apply these rules and concepts to the identifiers you assign to element name and id attributes, as well. Your scripts will then have no trouble using these identifiers in DOM object references.

Variable names are case-sensitive. Therefore, it is permissible (although not necessarily advisable) to reuse an identifier with different case letters to carry different values. One convention that you might employ is to determine which variables won’t be changing their values during the execution of your scripts (i.e., you will treat them as constants) and make their names all uppercase. Netscape 6 and later implement a forthcoming ECMAScript keyword called const , which you use in place of var to define a true constant value. No other browser supports this keyword yet, so you can use variables as constants and keep modification statements away from them.

JavaScript assigns data to a variable both “by reference” and “by value,” depending on the type of data. If the data is a true object of any kind (e.g., DOM object, array, custom object), the variable contains a “live” reference to the object. You may then use that variable as a substitute reference to the object:

var elem = document.getElementById("myTable");
var padWidth = elem.cellPadding;

But if the data is a simple value (string, number, Boolean), the variable holds only a copy of the value, with no connection to the object from which the value came. Therefore, the padWidth variable shown above simply holds a string value; if you were to assign a new value to the variable, it would have no impact on the table element. To set the object’s property, go back to the object reference and assign a value to the property:

elem.cellPadding = "10";

If an object’s property value is itself another object, the variable receives that data as an object reference, still connected to its object:

var elem = document.getElementById("myTable");
var elemStyle = elem.style;
elemStyle.fontSize = "18px";

Exercise care with DOM objects assigned to variables. It may seem as though the variable is a mere copy of the object reference, but changes you make to the variable value affect the document node tree.

See Also

Chapter 1, Chapter 2, and Chapter 3 discuss assigning values of different types—strings, numbers, arrays, and objects—to variables; Recipe 4.8 for the impact of variable name length on performance.

Creating a Named Function

NN 2, IE 3

Problem

You want to define a function that can be invoked from any statement in the page.

Solution

For a function that receives no parameters, use the simple function declaration format:

function myFunctionName( ) {
    // statements go here
}

If the function is designed to receive parameters from the statement that invokes the function, define parameter variable names in the parentheses following the function name:

function myFunctionName(paramVar1, paramVar2[, ..., paramVarN]) {
    // statements go here
}

You can define as many unique parameter variable identifiers as you need. These variables become local variables inside the function (var declarations are implied). Following JavaScript’s loosely typed conventions, parameter variables may hold any valid data type, as determined by the statement that invokes the function and passes the parameters.

Curly braces that contain the statements belonging to the function are required only when two or more statements are inside the function. It is good practice to use curly braces anyway, even for one-line statements, to assist in source code readability (a convention followed throughout this book).

The majority of long scripts throughout this book employ named functions, some with parameters, others without. Real-world examples abound, especially in recipes containing external JavaScript libraries, such as the DHTML API library in Recipe 13.3.

Discussion

A function is an object type in JavaScript, and the name you assign to the function becomes a case-sensitive identifier for that object. As a result, you cannot use a JavaScript-reserved keyword as a function name, nor should you use a function name that is also an identifier for one of your other global entities, such as variables or (in IE) element IDs. If you have two functions with the same name in a page, the one that comes last in source code order is the only available version. JavaScript does not implement the notion of function or method overloading found in languages such as Java (where an identically named method with a different number of parameter variables is treated as a separate method).

Invoke a function using parentheses:

myFunc( );
myFunc2("hello",42);

At times, you will need to assign a function’s reference to a property. For example, when you assign event handlers to element object properties (see Chapter 9), the assignment consists of a function reference. Such a reference is the name of the function but without parentheses, parameters, or quotes:

document.onclick = myFunc;

This kind of property assignment is merely setting the stage for a future invocation of the function.

Some programming languages distinguish between executable blocks of code that operate on their own and those that return values. In JavaScript, there is only one kind of function. If the function includes a return statement, the function returns a value; otherwise there is no returned value. Functions used as what other environments might call subroutines commonly return values simply because you define them to perform some kind of information retrieval or calculation, and then return the result to the statement that invoked the routine. When a function returns a value, the call to the function evaluates to a value that can be assigned immediately to a variable or be used as a parameter value to some other function or method. Recipe 15.6 demonstrates this feature. Its job is to display the part of the day (morning, afternoon, or evening) in a welcome greeting that is written to the page as it loads. A function called getDayPart( ) (defined in the head portion of the page) calculates the current time and returns a string with the appropriate day part:

function dayPart( ) {
    var oneDate = new Date( );
    var theHour = oneDate.getHours( );
    if (theHour < 12) {
        return "morning";
    } else if (theHour < 18) {
        return "afternoon";
    } else {
        return "evening";
    }
}

That function is invoked as a parameter to the document.write( ) method that places the text in the rendered page:

<script language="JavaScript" type="text/javascript">
<!--
document.write("Good " + dayPart( ) + " and welcome")
//-->
</script>
<noscript>Welcome</noscript>
 to GiantCo.

It is not essential to pass the same number of arguments to a function, as you have defined parameter variables for that function. For example, if the function is called from two different places in your script, and each place provides a different number of parameters, you can access the parameter values in the function by way of the arguments property of the function rather than by parameter variables:

function myFunc( ) {
    for (var i = 0; i < myFunc.arguments.length; i++) {
        // each entry in the arguments array is one parameter value
        // in the order in which they were passed
    }
}

A typical function (except a nested function, as described in Recipe 4.3) exists in the global context of the window housing the current page. Just as with global variables, these global functions can be referenced by script statements in other windows and frames. See Section 7.0.1 in Chapter 7 for examples of referencing content in other frames.

How large a function should be is a matter of style. For ease of debugging and maintenance, it may be appropriate to divide a long function into sections that either branch out to subroutines that return values or operate in sequence from one function to the next. When you see that you use a series of statements two or more times within a large function, these statements are excellent candidates for removal to their own function that gets called repeatedly from the large function.

The other stylistic decision in your hands is where you place the curly braces. This book adopts the convention of starting a curly brace pair on the same line as the function name, and closing the pair at the same tab location as the function declaration. But you can place the opening curly brace on the line below the function name, and left-align it if you like:

function myFunc( )
{
    // statements go here
}

Some coders feel this format makes it easier to keep brace pairs in sync. For a one-line function, the single statement can go on the same line as the function name:

function myFunc( ) {//statement goes here}

Adopt the style that makes the most logical sense to you and your code-reading eye.

See Also

Recipe 4.1 for a discussion about variables “by reference” and “by value”—a discussion that applies equally to function parameter variables; Recipe 4.3 for nesting functions.

Nesting Named Functions

NN 4, IE 4

Problem

You want to create a function that belongs to only one other function.

Solution

Starting with IE 4 and NN 4, you can nest a function inside another function according to the following syntax model:

function myFuncA( ) {
    // statements go here
    function.myFuncB( ) {
        // more statements go here
    }
}

In this construction, the nested function may be accessed only by statements in the outer function (but see the Discussion about Netscape 6 and later). Statements in the nested function have access to variables declared in the outer function, as well as to global variables. Statements in the outer function, however, do not have access to the inner function’s variables.

Discussion

The basic idea behind nested functions is that you can encapsulate all activity related to the outer function by keeping subroutine functions private to the outer function. Because the nested function is not directly exposed to the global space, you can reuse the function name in the global space or for a nested function inside some other outer function.

Netscape 6 and later extends the environment for nested functions in a logical and convenient way: a nested function acts as a method of the outer function object. There are some interesting ramifications with the way this feature is implemented. Consider the following test function set:

function myFuncA( ) {
    var valueA = "A";
    myFuncB( );
    function myFuncB( ) {
        var valueB = "B";
        alert(valueB);
        alert(valueA);
    }
}

When you invoke myFuncA( ) from the global space, it invokes its nested function, which dutifully displays the values of the two variables that are defined in the outer and inner functions. IE behaves the same way, as expected. But in Netscape 6 or later, you can invoke the nested function from the global space by way of the outer function:

myFuncA.myFuncB( );

Because the outer function does not execute in this direct access to myFuncB, the valueA variable is not initialized with any value. When myFuncB( ) runs, it shows the value of valueB, but valueA comes back as undefined. Therefore, access to the outer function’s variables is possible only when the inner function is invoked by the outer function.

See Also

Recipe 4.1 for a discussion of variable scope.

Creating an Anonymous Function

NN 4, IE 4

Problem

You want to define a function in the form of an expression that you can, for example, pass as a parameter to an object constructor or assign to an object’s method.

Solution

You can use an alternate syntax for defining functions without creating an explicitly named function (as shown in Recipe 4.2). Called an anonymous function, this syntax has all the components of a function definition except its identifier. The syntax model is as follows:

var someReference = function( ) {statements go here};

Statements inside the curly braces are semicolon-delimited JavaScript statements. You can define parameter variables if they’re needed:

var someReference = function(paramVar1[,..., paramVarN]) {statements go here};

Invoke the function via the reference to the function:

               someReference( );

Discussion

Anonymous function creation returns an object of type function. Therefore, you can assign the right side of the statement to any assignment statement where a function reference (the function name without parentheses) is expected. To demonstrate, we’ll make a version of a shortcut object constructor from Recipe 3.8. It starts with an ordinary function definition that gets invoked as a method of four objects defined with shortcut syntax:

function showAll( ) {
    alert("Employee " + this.name + " is " + this.age + " years old.");    
}
var employeeDB = [{name:"Alice", age:23, show:showAll},
                  {name:"Fred", age:32, show:showAll},
                  {name:"Jean", age:28, show:showAll},
                  {name:"Steve", age:24, show:showAll}];

Notice how in the object constructors, a reference to the showAll( ) function is assigned to the show method name. Invoking this method from one of the objects is done in the following manner:

employeeDB[2].show( );

For the sake of example, we assign an anonymous function to the first object. The anonymous function is custom-tailored for the first object and replaces the reference to showAll( ):

var employeeDB = [{name:"Alice", age:23, show
                       :function( ) 
                      {alert("Alice\'s age is not open to the public.")}},
                  {name:"Fred", age:32, show:showAll},
                  {name:"Jean", age:28, show:showAll},
                  {name:"Steve", age:24, show:showAll}];

Now, if you invoke employeeDB[0].show( ), the special alert displays itself because the anonymous function is running instead of the showAll( ) function. We have saved the need to create an external function with its own identifier just to act as an intermediary between the show method name and the statements to execute when the method is invoked.

See Also

Recipe 4.2 for creating traditional named functions.

Delaying a Function Call

NN 2, IE 3

Problem

You want a function to run at a specified time in the near future.

Solution

Use the window.setTimeout( ) method to invoke a function once after a delay of a number of milliseconds. You essentially set a timer to trigger a function of your choice. The function is referenced as a string, complete with parentheses, as in the following example:

var timeoutID = setTimeout("myFunc( )", 5000);

The method returns an ID for the time-out operation and should be preserved in a global variable. If, at any time before the delayed function fires, you wish to abort the timer, invoke the clearTimeout( ) method with the time-out ID as the parameter:

clearTimeout(timeoutID);

Once the timer is set, other script processing may proceed as usual, so it is often a good idea to place the setTimeout( ) call as the final statement of a function.

Discussion

It’s important to understand what the setTimeout( ) method doesn’t do: it does not halt all processing in the manner of a delay that suspends activity until a certain time. Instead, it simply sets an internal countdown timer that executes the named function when the timer reaches zero. For example, if you are creating a slide show that should advance to another page after 15 seconds of inactivity from the user, you would initially set the timer via the onload event handler for the page and the resetTimer( ) function:

var timeoutID;
function goNextPage( ) {
    location.href = "slide3.html";
}
function resetTimer( ) {
    clearTimeout(timeoutID);
    timeoutID = setTimeout("goNextPage( )", 15000);
}

You would also set an event handler for, say, the onmousemove event so that each time the user activates the mouse, the autotimer resets to 15 seconds:

window.onmousemove = resetTimer;

The resetTimer( ) function automatically cancels the previously set time-out before it triggers the goNextPage( ) function, and then it starts a new 15-second timer.

If the function you are invoking via the delay requires parameters, you can assemble a string with the values, even if those values are in the form of variables within the function. But—and this is important—the variable values cannot be object references. Parameters must be in a form that will survive the conversion to the string needed for the first argument of the setTimeout( ) method. Recipe 8.4 demonstrates how you can convey names of DOM form-related objects as ways of passing an object reference. The tricky part is in keeping the quotes in order:

function isEMailAddr(elem) {
    var str = elem.value;
    var re = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
    if (!str.match(re)) {
        alert("Verify the e-mail address format.");
        setTimeout("focusElement('" + elem.form.name + "', '" + elem.name + "')", 0);
        return false;
    } else {
        return true;
    }
}

In this example, the focusElement( ) function requires two parameters that are used to devise a valid reference to both a form object and a text input object. Both parameters of the focusElement( ) function are strings. Because the first argument of setTimeout( ) is, itself, a string, you have to force the “stringness” of the parameters to focusElement( ) by way of single quotes placed within the extended string concatenation sequence. (The zero milliseconds shown in the example is not a mistake for this application. Learn why in the discussion for Recipe 8.4.)

If you are looking for a true delay between the execution of statements within a function or sections of a function, JavaScript has nothing comparable to commands available in some other programming languages. But you can accomplish the same result by dividing the original function into multiple functions—one function for each section that is to run after a delay. Link the end of one function to the next by ending each function with setTimeout( ), which invokes the next function in sequence after the desired amount of time:

function mainFunc( ) {
    // initial statements here
    setTimeout("funcPart2( )", 10000);
}
function funcPart2( ) {
    // initial statements here
    setTimeout("finishFunc( )", 5000);
}
function finishFunc( ) {
    // final batch of statements here
}

The related functions don’t have to be located adjacent to each other in the source code. If all related functions need to operate on the same set of values, you can cascade them as parameters (provided the parameters can be represented as nonobject values), or you can preserve them as global variables. If the values are related, it may be a good reason to define a custom object with values assigned to labeled properties of that object to make it easier to see at a glance what each function segment is doing with or to the values.

Another JavaScript method, setInterval( ) , operates much like setTimeout( ), but repeatedly invokes the target function until told to stop (via the clearInterval( ) method). The second parameter (an integer in milliseconds) controls the amount of time between calls to the target function.

See Also

Recipe 8.4 for using setTimeout( ) to keep script execution synchronized; Recipe 10.11 for a sequence of three initialization functions that are linked via calls to setTimeout( ) to keep execution synchronicity in order; Recipe 12.3 for an example of using a self-contained counter variable in a repeatedly invoked function to execute itself a fixed number of times; Recipe 13.9 and Recipe 13.10 for applications of setInterval( ) in animation.

Branching Execution Based on Conditions

NN 4, IE 4

Problem

You want your scripts to execute sections of code based on external values, such as Booleans, user entries in text boxes, or user choices from select elements.

Solution

Use the if , if /else, or switch flow control constructions to establish an execution path through a section of your scripts. When you need to perform a special section of script if one condition is met only, use the simple if construction with a conditional expression that tests for the condition:

if (condition) {
    // statements to execute if condition is true
}

To perform one branch under one condition and another branch for all other situations, use the if/else construction:

if (condition) {
    // statements to execute if condition is true
} else {
    // statements to execute if condition is false
}

You can be more explicit in the else clause by performing additional condition tests:

if (conditionA) {
    // statements to execute if conditionA is true
} else if (conditionB) {
    // statements to execute of conditionA is false and conditionB is true
} else {
    // statements to execute if both conditionA and conditionB are false
}

For multiple conditions, you should consider using the switch statement if the conditions are based on string or numeric value equivalency:

switch (expression) {
    case valueA:
        // statements to execute if expression evaluates to valueA
        break;
    case valueB:
        // statements to execute if expression evaluates to valueB
        break;
    ...
    default:
        // statements to execute if expression evaluates to no case value
}

The break statements in each of the case branches assure that the default branch (which is optional) does not also execute.

Discussion

A condition expression in the if and if/else constructions is an expression that evaluates to a Boolean true or false. Typically, such expressions use comparison operators (= =, = = =, !=, != =, <, <=, >, >=) to compare the relationship between two values. Most of the time, you are comparing a variable value against some constant or known value:

var theMonth = myDateObj.getMonth( );
if (theMonth =  = 1) {
    // zero-based value means the date is in February
    monLength = getLeapMonthLength(myDateObj);
} else {
    monLength = getMonthLength(theMonth);
}

JavaScript offers some additional shortcut evaluations for condition expressions. These shortcuts come in handy when you need to branch based on the existence of an object or property. Table 4-1 lists the conditions that automatically evaluate to true or false when placed inside the parentheses of a condition expression. For example, the existence of an object evaluates to true, which allows a construction such as the following to work:

if (myObj) {
    // myObj exists, so use it
}
Table 4-1. Condition expression equivalents

true

false

String has one or more characters

Empty string

Number other than zero

0

Nonnull value

null

Referenced object exists

Referenced object does not exist

Object property is defined and evaluates to a string of one or more characters or a nonzero number

Object property is undefined, or its value is an empty string or zero

When testing for the existence of an object property (including a property of the global window object), be sure to start the reference with the object, as in the following:

if (window.innerHeight) { ... }

But you also need to be careful when testing for the existence of a property if there is a chance that its value could be an empty string or zero. Such values force the conditional expression to evaluate to false, even though the property exists. Therefore, it is better to test for the data type of the property with the typeof operator. If you’re not sure about the data type, test the data type against the undefined constant:

if (typeof myObj.myProperty != "undefined" ) {
    // myProperty exists and has a value of some kind assigned to it
}

If there is a chance that neither the object nor its property exists, you need to group together conditional expressions that test for the existence of both. Do this by testing for the object first, then the property. If the object does not exist, the expression short-circuits the test of the property:

if (myObj && typeof myObj.myProperty != "undefined") {
    // myObj exists, and so does myProperty
}

If you test for the property first, the test fails with a script error because the expression with the object fails unceremoniously.

JavaScript also provides a shortcut syntax that lets you avoid the curly braces for simple assignment statements that execute differently based on a condition. The syntax is as follows:

var myValue = (condition) ? value1 : value2;

If the condition evaluates to true, the righthand expression evaluates to the first value; otherwise, it evaluates to the second value. For example:

var backColor = (temperature > 100) ? "red" : "blue";

Several recipes in later chapters use this shortcut construction frequently, even to two levels deep. For example:

var backColor = (temperature > 100) ? "red" : ((temperature < 80) ? 
    "blue" : "yellow");

This shortcut expression is the same as the longer, more readable, but less elegant version:

var backColor ;
if (temperature > 100) {
    backColor = "red";
} else if (temperature < 80) {
    backColor = "blue";
} else {
    backColor = "yellow";
}

When you have lots of potential execution branches, and the triggers for the various branches are not conditional expressions per se, but rather the value of an expression, then the switch construction is the way to go. In the following example, a form contains a select element that lets a user choose a size for a product. Upon making that choice, an onchange event handler in the select element triggers a function that inserts the corresponding price for the size in a text box:

function setPrice(form) {
    var sizeList = form.sizePicker;
    var chosenItem = sizeList.options[sizeList.selectedIndex].value;
    switch (chosenItem) {
        case "small" :
            form.price.value = "44.95";
            break;
        case "medium" :
            form.price.value = "54.95";
            break;
        case "large" :
            form.price.value = "64.95";
            break;
        default:
             form.price.value = "Select size";
    }
}

If the switch expression always evaluates to one of the cases, you can omit the default branch, but while you are in development of the page, you might leave it there as a safety valve to alert you of possible errors if the expression should evaluate to an unexpected value.

See Also

Most of the recipes in Chapter 15 use the shortcut conditional statement to equalize disparate event models.

Handling Script Errors Gracefully

NN 6, IE 5

Problem

You want to process all script errors out of view of users, and thus prevent the browser from reporting errors to the user.

Solution

The quick-and-dirty, backward-compatible way to prevent runtime script errors from showing themselves to users is to include the following statements in a script within the head portion of a page:

function doNothing( ) {return true;}
window.onerror = doNothing;

This won’t stop compile-time script errors (e.g., syntax errors that the interpreter discovers as the page loads). It also won’t reveal to you, the programmer, where errors lurk in your code. Add this only if you need to deploy a page before you have fully debugged the code; remove it to test your code.

In IE 5 or later and Netscape 6 or later, you can use more formal error (exception) handling. To prevent earlier browsers from tripping up on the specialized syntax used for this type of processing, embed these statements in <script> tags that specify JavaScript 1.5 as the language attribute (language="JavaScript1.5").

Wrap statements that might cause (throw) an exception in a try /catch construction. The statement to execute goes into the try section, while the catch section processes any exception that occurs:

<script type="text/javascript" language="JavaScript1.5">
function myFunc( ) {
    try {
        // statement(s) that could throw an error if various conditions aren't right
    }
    catch(e) {
        // statements that handle the exception (error object passed to e variable)
    }
}
</script>

Even if you do nothing in the catch section, the exception in the try section is not fatal. Subsequent processing in the function, if any, goes on, provided it is not dependent upon values created in the try section. Or, you can bypass further processing in the function and gracefully exit by executing a return statement inside the catch section.

Discussion

Each thrown exception generates an instance of the JavaScript Error object. A reference to this object reaches the catch portion of a try/catch construction as a parameter to the catch clause. Script statements inside the catch clause may examine properties of the object to learn more about the nature of the error. Only a couple of properties are officially sanctioned in the ECMAScript standard so far, but some browsers implement additional properties that contain the same kind of information you see in script error messages. Table 4-2 lists informative Error object properties and their browser support.

Table 4-2. Error properties

Property

IE/Windows

IE/Mac

NN

Description

description

5

5

n/a

Plain-language description of error

fileName

n/a

n/a

6

URI of the file containing the script throwing the error

lineNumber

n/a

n/a

6

Source code line number of error

message

5.5

n/a

6

Plain-language description of error (ECMA)

name

5.5

n/a

6

Error type (ECMA)

number

5

5

n/a

Microsoft proprietary error number

Error messages are never intended to be seen by users. Use the description or message property of an error object in your own exception handling to decide how to process the exception. Unfortunately, the precise message from the various browsers is not always identical for a given error. For example, if you try to reference an undefined object, IE reports the description string as:

'myObject' is undefined

Netscape 6 or later, on the other hand, reports:

myObject is not defined

This makes cross-browser exception handling a bit difficult. In this case, you could try to fudge it by performing string lookups (regular expression matches) for the object reference and the fragment “defined” as in the following:

try {
    window.onmouseover = trackPosition;
}
catch(e) {
    var msg = (e.message) ? e.message : e.description;
    if (/trackPosition/.exec(msg) && /defined/.exec(msg)) {
        // trackPosition function does not exist within page scope
    }
}

You can also intentionally throw an exception as a way to build exception handling into your own processing. The following function is a variation of a form validation function that tests for the entry of only a number in a text box. The try clause tests for an incorrect value. If found, the clause creates its own instance of an Error object and uses the throw method to trigger an exception. Of course, the thrown exception is immediately caught by the following catch clause, which displays the alert message and refocuses the text box in question:

function processNumber(inputField) {
    try {
        var inpVal = parseInt(inputField.value, 10);
        if (isNaN(inpVal)) {
            var msg = "Please enter a number only.";
            var err = new Error(msg);
            if (!err.message) {
                err.message = msg;
            }
            throw err;
        }
        // it's safe to process number here
    }
    catch (e) {
        alert(e.message);
        inputField.focus( );
        inputField.select( );
    }
}

This kind of function is invoked by both an onchange event handler for the text field and a batch validation routine, as described in Chapter 8.

Improving Script Performance

NN 2, IE 3

Problem

You want to speed up a sluggish script.

Solution

When swallowing small doses of code, JavaScript interpreters tend to process data speedily. But if you throw a ton of complex and deeply nested code at a browser, you may notice some latency, even if all the data is downloaded in the browser.

Here are a handful of useful tips to help you unclog potential processing bottlenecks in your code:

  • Avoid using the eval( ) function

  • Avoid the with construction

  • Minimize repetitive expression evaluation

  • Use simulated hash tables for lookups in large arrays of objects

  • Avoid multiple document.write( ) method calls

Look for these culprits especially inside loops, where delays become magnified.

Discussion

One of the most inefficient functions in the JavaScript language is eval( ). This function converts a string representation of an object to a genuine object reference. It becomes a common crutch when you find yourself with a string of an object’s name or ID, and need to build a reference to the actual object. For example, if you have a sequence of mouse rollover images comprising a menu, and their names are menuImg1, menuImg2, and so on, you might be tempted to create a function that restores all images to their normal image with the following construction:

for (var i = 0; i < 6; i++) {
    var imgObj = eval("document.menuImg" + i);
    imgObj.src = "images/menuImg" + i + "_normal.jpg";
}

The temptation is there because you are also using string concatenation to assemble the URL of the associated image file. Unfortunately, the eval( ) function in this loop is very wasteful.

When it comes to referencing element objects, there is almost always a way to get from a string reference to the actual object reference without using the eval( ) function. In the case of images, the document.images collection (array) provides the avenue. Here is the revised, more streamlined loop:

for (var i = 0; i < 6; i++) {
    var imgObj = document.images["menuImg" + i];
    imgObj.src = "images/menuImg" + i + "_normal.jpg";
}

If an element object has a name or ID, you can reach it through some collection that contains that element. The W3C DOM syntax for document.getElementById( ) is a natural choice when working in browsers that support the syntax and you have the element’s ID as a string. But even for older code that supports names of things like images and form controls, there are collections to use, such as document.images and the elements collection of a form object (document.myForm.elements["elementName"]). For custom objects, see the later discussion about simulated hash tables. Hunt down every eval( ) function in your code and find a suitable, speedier replacement.

Another performance grabber is the with construction. The purpose of this control statement is to help narrow the scope of statements within a block. For example, if you have a series of statements that work primarily with a single object’s properties and/or methods, you can limit the scope of the block so that the statements assume properties and methods belong to that object. In the following script fragment, the statements inside the block invoke the sort( ) method of an array and read the array’s length property:

with myArray {
    sort( );
    var howMany = length;
}

Yes, it may look efficient, but the interpreter goes to extra lengths to fill in the object references before evaluating the nested expressions. Don’t use this construction.

It takes processing cycles to evaluate any expression or reference. The more “dots” in a reference, the longer it takes to evaluate the reference. Therefore, you want to avoid repeating a lengthy object reference or expression if it isn’t necessary, especially inside a loop. Here is fragment that may look familiar to you from your own coding experience:

function myFunction(elemID) {
    for (i = 0; i < document.getElementById(elemID).childNodes.length; i++) {
        if (document.getElementById(elemID).childNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

In the course of this function’s execution, the expression document.getElementById( ) evaluates twice as many times as there are child nodes in the element whose ID is passed to the function. At each start of the for loop’s execution, the limit expression evaluates the method; then the nested if condition evaluates the same expression each time through the loop. More than likely, additional statements in the loop evaluate that expression to access a child node of the outer element object. This is very wasteful of processing time.

Instead, at the cost of one local variable, you can eliminate all of this repetitive expression evaluation. Evaluate the unchanging part just once, and then use the variable reference as a substitute thereafter:

function myFunction(elemID) {
    var elem = document.getElementById(elemID);
    for (i = 0; i < elem .childNodes.length; i++) {
        if (elem .childNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

If all of the processing inside the loop is with only child nodes of the outer loop, you can further compact the expression evaluations:

function myFunction(elemID) {
    var elemNodes = document.getElementById(elemID).childNodes;
    for (i = 0; i < elemNodes.length; i++) {
        if (elemNodes[i].nodeType =  = 1) {
            // processing element nodes here
        }
    }
}

As an added bonus, you have also reduced the source code size. If you find instances of repetitive expressions whose values don’t change during the course of the affected script segment, consider them candidates for pre-assignment to a local variable.

Next, eliminate time-consuming iterations through arrays, especially multidimensional arrays or arrays of objects. If you have a large array (say, more than about 100 entries), even the average lookup time may be noticeable. Instead, use the techniques shown in Recipe 3.9 to perform a one-time generation of a simulated hash table of the array. Assemble the hash table while the page loads so that any delay caused by creating the table is blended into the overall page-loading time. Thereafter, lookups into the array will be nearly instantaneous, even if the item found is the last item in the many-hundred member array.

The final tip addresses use of the document.write( ) method to generate content while the page loads. Treat this method as if it were an inherently slow input/output type of operation. Invoke the method as infrequently as possible. If you are writing a lot of content to the page, accumulate the HTML into a string variable, and blast it to the page with one call to document.write( ).

See Also

Recipe 3.9 for details on creating a simulated hash table from an array; Recipe 3.13 for a rare case where the eval( ) function can’t be avoided.

Get JavaScript & DHTML Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.