Because variables are one of the most important aspects of any programming language, awareness of the implications of variable declaration and reference is key to writing clear, well-behaved code. Choosing good names for variables is important, and so is understanding how to tell exactly which variable a name refers to.
An identifier is a name by which a variable or function is known. In JavaScript, any combination of letters, digits, underscores, and dollar signs is allowed to make up an identifier. The only formal restrictions on identifiers are that they must not match any JavaScript reserved words or keywords and that the first character cannot be a digit. Keywords are the words of the JavaScript language, such as string, Object, return, for, and while. Reserved words are words that might become keywords in the future. You can find a comprehensive list of reserved words and keywords in Appendix E.
One of the most important aspects of writing clear, understandable code is choosing appropriate names for your variables. Unreasonably long or incomprehensible identifiers should be avoided.
Although JavaScript allows you to give a variable a cryptic name like _$0_$, doing so in practice is a bad idea. Using dollar signs in your identifiers is highly discouraged; they are intended for use with code generated by mechanical means and were not supported until JavaScript 1.2. Despite its common use in practice, beginning an identifier with an underscore is also not a good idea. Variables internal to the interpreter often begin with two underscores, so using a similar naming convention can cause confusion.
A variable’s name should give some information about its purpose or value that is not immediately apparent from its context. For example, the following identifiers are probably not appropriate:
var _ = 10; var x = "George Washington"; var foobar = 3.14159; var howMuchItCostsPerItemInUSDollarsAndCents = "$1.25";
More apropos might be
var index = 10; var president = "George Washington"; var pi = 3.14159; var price = "$1.25";
You should also use appropriate names for composite types. For example,
var anArray = ["Mon", "Tues", "Wed", "Thurs", "Fri"];
is a poor choice of identifier for this array. Later in the script it is not at all clear what value anArray[3] might be expected to have. Better is
var weekdays = ["Mon", "Tues", "Wed", "Thurs", "Fri"];
which when later used as weekdays[3] gives the reader some idea of what the array contains. Function names are similar. A function to sum items could be called calc() but it might be better as sumAll().
Because JavaScript is case-sensitive, weekdays and weekDays refer to two different variables. For this reason, it is not advisable to choose identifiers that closely resemble each other. Similarly, it is not advisable to choose identifiers close to or identical to common objects or properties. Doing so can lead to confusion and even errors. Capitalization does, however, play an important role in naming conventions. JavaScript programmers are fond of the camel-back style for variable capitalization. With this convention, each word in a variable name has an initial capital except for the first. For example, a variable holding the text color of the body of the document might be named bodyTextColor. This convention is consistent with how properties of browser objects are named, so new programmers are strongly encouraged to adopt its use.
JavaScript programmers are fond of using very short variable names, like x, in order to decrease the number of characters that need to be transferred to the client. The reason is that fewer characters to send implies faster download time. Although the end user might notice some difference in download time for very large scripts, when compared to the size of typical images found in Web pages today, the several hundred characters saved by using short variable names is almost inconsequential. In addition, JavaScript stripped of comments and descriptive variable names is very hard to decipher, though that may be intentional. Consider that you may not want anyone reading or understanding your code. However, this can be a very bad thing if anyone else but you is expected to maintain or fix your scripts. It is possible to provide the best of both worlds by using any number of automated JavaScript “crunching” tools to carry out this task before you publish them to your Web site. In this sense, as with normal code, you would keep your original scripts intact in a readable form, but get the speed improvements of having crunched and obfuscated JavaScript on your site. One good tool for doing this is the W3Compiler found at www.w3compiler.com.
If more than one person works on JavaScripts in your organization, or if you maintain a large collection of scripts (say, more than half a dozen), it’s a very good idea to adopt a naming convention for your variables. Developing and sticking to a consistent style of naming improves the readability and maintainability of your code.
As we have seen in numerous examples, variables are declared with the var keyword. Multiple variables can be declared at once by separating them with a comma. Variables may also be initialized with a starting value by including an assignment in the declaration. All of the following are legal variable declarations:
var x; var a, b, c; var pi, index = 0, weekdays = ["M", "T", "W", "Th", "F"];
In the final declaration, pi is assigned the undefined value, index is initialized to zero, and weekdays is initialized to a five-element array.
One “feature” of JavaScript is implicit variable declaration. When you use an undeclared variable on the left-hand side of an assignment, the variable is automatically declared.
For example, many developers opt for
numberOfWidgets = 5;
versus
var numberOfWidgets; numberOfWidgets = 5;
or
var numberOfWidgets = 5;
While it would seem the first choice is easier, the truth of the matter is that implicit declaration is terrible programming style and should never be used. One reason is that readers cannot differentiate an implicit variable declaration from a reference to a variable of the same name in an enclosing scope. Another reason is that implicit declaration creates a global variable even if used inside of a function. Use of implicit declaration leads to sloppy coding style, unintentional variable clobbering, and unclear code—in short, do not use it.
The scope of a variable is all parts of a program where it is visible. Being visible means that the variable has been declared and is available for use. A variable that is visible everywhere in the program has global scope. A variable that is visible only in a specific context—a function, for example—has local scope. A context is the set of defined data that make up the execution environment. When the browser starts, it creates the global context in which JavaScript will execute. This context contains the definitions of the features of the JavaScript language (the Array and Math objects, for example) in addition to browser-specific objects like Navigator.
Note |
If you’re a JavaScript beginner, you may wish to only skim the next few sections on scope, and come back to them when you’re more comfortable with the language. |
When a function is invoked, the interpreter creates a new local context for the duration of its execution. All variables declared in the function (including its arguments) exist only within this context. When the function returns, the context is destroyed. So, if you wish to preserve a value across multiple function calls, you might need to declare a global variable.
Note |
JavaScript lacks static variables like C—you would have to use global variables to achieve this effect. |
When a variable is referenced in a function, the interpreter first checks the local context for a variable of that name. If the variable has not been declared in the local context, the interpreter checks the enclosing context. If it is not found in the enclosing context, the interpreter repeats the process recursively until either the variable is found or the global context is reached.
It is important to note that the contexts are checked with respect to the source code and not the current call tree. This type of scoping is called static scoping (or lexical scoping). In this way, locally declared variables can hide variables of the same name that are declared in an enclosing context. The following example illustrates variable hiding:
var scope = "global"; function myFunction() { var scope = "local"; document.writeln("The value of scope in myFunction is: " + scope); } myFunction(); document.writeln("The value of scope in the global context is: " + scope);
The result is shown in Figure 3-2. The local variable scope has hidden the value of the global variable named scope. Note that omitting var from the first line of myFunction would assign the value "local" to the global variable scope.
There are some important subtleties regarding variable scope. The first is that each browser window has its own global context. So it is unclear at first glance how to access and manipulate data in other browser windows. Fortunately, JavaScript enables you to do so by providing access to frames and other named windows. The mechanics of cross-window interaction is covered in later chapters, particularly Chapter 12.
The second subtlety related to scoping is that, no matter where a variable is declared in a context, it is visible throughout that context. This implies that a variable declared at the end of a function is visible throughout the whole function. However, any initialization that is included in the declaration is only performed when that line of code is reached. The result is that it is possible to access a variable before it is initialized, as in the following example:
function myFunction() { document.writeln("The value of x before initialization in myFunction is: ", x); var x = "Hullo there!"; document.writeln("The value of x after initialization in myFunction is: ", x); } myFunction();
The result is shown in Figure 3-3. Note how scope has undefined value before it is initialized.
The third subtlety has to do with static scoping. Consider the following code,
var scope = "global"; function outerFunction() { var scope = "local"; innerFunction(); } function innerFunction() { alert("The value of scope is: " + scope); } outerFunction();
which results in:
This example illustrates a critical aspect of static scoping: the value of scope seen in innerFunction is the value present in enclosing the global context: "global." It does not see the value set in outerFunction. That value of scope is local to that function and not visible outside of it. The correct value for scope was found by examination of the enclosing context in the original JavaScript source code. The interpreter can infer the correct value by “static” examination of the program text. Hence the name “static scoping.”
We saw that variables declared inside functions are local to that function. The same rule applies to JavaScript included in event handlers: the text of the event handler is its own context. The following script illustrates this fact. It declares a global variable x as well as a variable x within an event handler:
<<script type="text/javascript">> var x = "global"; <</script>> <<form action="#" method="get">> <<input type="button" value="Mouse over me first" onmouseover="var x = 'local'; alert('Inside this event hander x is ' + x);" />> <<input type="button" value="Mouse over me next! " onmouseover="alert('Inside this event hander x is ' + x);" />> <</form>>
Move the mouse over the first button to see that the value of x in that context has been set to "local". You can see that that x is not the same as the global x by then moving the mouse over the second button. The value printed by the second button is "global", indicating that the x set in the first handler was not the global variable of the same name.
Remember that because JavaScript is statically scoped, it’s only variables declared within the text of an event handler that have their own context. Consider this example:
<<script type="text/javascript">> var x = "global"; function printx() { alert("Inside this function x is " + x); } <</script>> <<form action="#" method="get">> <<input type="button" value="Mouse over me!" onmouseover="var x = 'local'; printx();" />> <</form>>
You can see that the value of x that is printed is "global". Static scoping at work again: Since the context of the function printx is global, it doesn’t see the local value set in the event handler text.
The preceding discussion of how variable names are resolved hints at the fact that execution contexts vary dynamically and reside within one another. For example, if a variable referenced in the text of an event handler cannot be found within that event handler’s context, the interpreter “widens” its view by looking for a global variable of the same name. You can think of the event handler’s local context as residing within the global context. If a name can’t be resolved locally, the enclosing (global) scope is checked.
In fact, this is exactly the right way to think about execution contexts in JavaScript. An (X)HTML document can be thought of as a series of embedded contexts: an all-enclosing JavaScript global scope within which resides a browser context, within which resides the current window. Inside the window resides a document, within which might be a form containing a button. If script executing in the context of the button references a variable not known in the button’s context, the interpreter would first search the form’s context, then the document’s, then the window’s, the browser’s, and eventually the global context.
The exact details of how this works comprise JavaScript’s object model, a subject discussed in later chapters. A comprehensive knowledge of the topic is not really required to program in JavaScript, but helps tremendously in understanding where the objects available to your scripts come from, and how they are related. It will also go a long way in setting you apart from the typical JavaScript developer!