Notice: This material is excerpted from Special Edition Using JavaScript, ISBN: 0-7897-0789-6. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.
by Mark Reynolds
There are many, many different computer programming languages in use today. Each has its own set of special features, which are highly praised by its fans and vigorously panned by its detractors. If you have worked in more than one language then you are aware that there is a continuum of language styles, ranging from highly structured languages such as Ada to more free-wheeling ones such as Lisp. Many are associated with specific settings or applications. Ada, for example, is often found in military projects, while Lisp is often associated with artificial intelligence. Some languages, such as HTML, the language used to describe the layout of World Wide Web pages, have a well defined organizational structure, but have very little in the way of traditional program structure (there are no data types, for example).
In trying to understand a new language it is not only important to master its syntax, it is also vital to appreciate its style-the way in which that language can be used to accomplish specific goals. We have already reviewed the basic goals of JavaScript in Chapter 1, "What Is JavaScript?" as well as contrasting it to the more structured Java language. This chapter describes the JavaScript language from both perspectives. A thorough description of its syntax is given, and some initial concepts on how to structure a JavaScript program are also introduced. Anyone who has programming in almost any modern declarative language, such as C, C++ or Pascal, will feel immediately at home. In addition, HTML authors who have never programmed will be able to rapidly acquire JavaScript proficiency.
This chapter teaches the syntax and structure of the JavaScript language. You will learn how to do the following:
JavaScript is based on an action oriented model of the World Wide Web. Elements of a Web page, such as a button or checkbox, may trigger actions or events. When one of these events occurs, a corresponding piece of JavaScript code, usually a JavaScript function, is executed. That function, in turn, is composed of various statements which perform calculations, examine or modify the contents of the Web page, or perform other tasks in order to respond in some way to that event. For example, pressing the SUBMIT button on an online order form might invoke a JavaScript function that validates the contents of that form to ensure that the user entered all the required information.
In this section we examine the syntax of JavaScript from the bottom up. We begin with the most basic concepts of how to write a JavaScript statement, and what that statement does, and progress upward through more complex and powerful structures in subsequent sections, culminating in a detailed discussion of JavaScript functions and related concepts. Chapter 3, "Events and JavaScript," explores in greater detail how these elements are tied into Web pages through events.
In general, the elements of a JavaScript program can be divided into five categories, as follows:
This set of categories is very similar to many other languages. As we examine each of these elements in subsequent sections we will discover that JavaScript is somewhat minimalist in its approach. Many familiar elements, such as explicit data types (int, String, REAL), are missing or have been substantially simplified. However, JavaScript also provides a number of powerful object-oriented constructs which greatly simplify program organization. In this way JavaScript has the expressive power of languages such as C or Java, while also having fewer rules to remember
One of the main differences between JavaScript and most other languages is that it does not have explicit data types. There is no way to specify that a particular variable represents an integer, a string, or a floating-point (real) number. Any JavaScript variable can be any of these-in fact, the same variable can be interpreted differently in different contexts.
All JavaScript variables are declared using the keyword var. A variable may be initialized, meaning that it is given a value when it is declared, or it may be uninitialized. In addition, multiple variables can be declared on the same line by separating their names with commas. For example, the statements
declare a variable named x with initial value 7, an uninitialized variable y and variables named z and lk whose initial values are "19" and "lucky", respectively. It might seem that x is an integer, z and lk are strings, and y is some undefined quantity. In fact, the real story is a little more complicated than this. The value of each variable depends on the context in which it is used. This context is related to the order in which the variables are seen. As you might guess, the expressions
evaluate to 12 and "lucky19", seemingly confirming our suspicions about what they really are. However, it is also possible to form the expressions
which evaluates to "lucky7" and 26, respectively. In the first expression, x has been interpreted as a string, while in the second, z has been interpreted as an integer.
JavaScript often attempts to treat all variables within a statement as if they had the same type as the first variable in the statement.
These examples illustrate two critically important points about the JavaScript language. First, while JavaScript does not have explicit data types, it does have implicit data types. Second, JavaScript has a set of conversion rules that allow it to decide how to treat a value based on the context in which it is used. The context is establish by reading the expression from left to right. In the expression x + z, for example, x is implicitly a numerical value, so that JavaScript also attempts to view z as a number and perform the sum numerically. It succeeds, and the expected 26 results.
What would have happened if we had tried x + lk? The x variable occurs first on the left, and is really a number at heart. JavaScript thus tries to interpret the variable lk as a number, too. This is extremely unlucky, in fact, because "lucky" cannot be converted to a number (while z, the string "19" could). JavaScript reports an error if asked to evaluate x + lk. To understand JavaScript variables and values, therefore, it is necessary to understand its set of implicit types and how they may be converted to one another.
Before we enter into these details, let us consider one final example. In all the preceding cases, the uninitialized variable y was never used. What would be the value of an expression such as
Of course, as in all other programming languages, the result of using an uninitialized variable is never good. Since y has never been given a value, there is no way this expression can be evaluated. It may result in something seemingly innocent, such as x being assigned the value of z, as if y were zero. It may also result in something much more serious, such as the value of x becoming something strange, or, more likely, a JavaScript error occurring. This leads to the following common sense rule.
Initialize all JavaScript variables to meaningful default values. If a variable has no meaningful default, initialize it to null.
There are five major implicit data types in JavaScript. A JavaScript value may be as follows:
Actually, it would be more correct to say that there are five categories of data type, since it is possible to distinguish two different types of numbers (integers and floating-point numbers), and many different types of JavaScript objects, functions, and other structured types. In fact, Part II of those book, "JavaScript Objects," is entirely devoted to explaining the many different JavaScript objects.
It is very important to distinguish between variables and their values. The statement x = 10 contains two components: the variable x and the literal value 10. A literal refers to anything that is referred to directly, by its actual value. A variable is just an abstraction that provides a way of giving names to values. Thus the statement x = 10 says, "I am going to refer to the concrete (literal) quantity 10 by the abstract (variable) name x," just as you might say, "I am going to call this lumpy thing I'm sitting on a chair." This also leads to the following important piece of advice.
It is bad practice to change the implicit data type of a variable. If a variable is initialized to have a certain type (such as string) it should always have that type.
Thus, since we have started out with x = 10 we should make sure that x always has some numeric value. There is no rule that prohibits us from later saying x = "Fortran", but this will generally lead to confusion or programming errors in most cases. No one will stop you from calling that lumpy thing you are sitting on "bacon and eggs" but many of your guests may become confused if you do so.
One final rule about variable names: a valid JavaScript variable name must begin with a letter or with the underscore character (_). Case is important, so that norl, NoRl, NORL, and _NORL are all valid JavaScript variable names that refer to different variables.
There are two numeric types in JavaScript: integers and floating-point numbers. The rules for specifying both types are almost identical to those of C or C++ or Java. Integers may be specified in base 10 (decimal), base 8 (octal), or base 16 (hexadecimal) formats. The three forms are distinguished as follows, based on the first one or two characters:
Any of the three forms can also start with a + or - sign. Thus,
-45 is a decimal integer, 017 is an octal integer, and 0x12EF5
is a hexadecimal integer. The minimum and maximum integers that
can be used are implementation dependent, but at least 32 bits
should be expected.
Floating-point numbers can be specified in either the standard decimal point (".") format or the engineering E-notation. Typical floating-point numbers should contain a decimal point or an exponent, which may begin with either e or E. A floating-point number may also have a + or - sign. 0.0, -1.4e12, and 3.14159 are all valid floating-point numbers. The range of valid floats is again implementation dependent, but you should expect that any valid short floating-point number, as defined by the IEEE standard, is acceptable. (The IEEE is the Institute of Electrical and Electronics Engineers, a professional and standards-making organization.)
Note that the original LiveScript language attempted to treat all the numeric types the same. Since it has become JavaScript there has been a convergence toward the numerical types of the Java language, and the distinction between integer values, such as 5, and floating-point (or real) values, such as 3.3333, has increased.
LiveScript is now completely obsolete. It has been replaced by its descendant, JavaScript. Avoid any code you encounter labeled LiveScript as it will almost certainly not work correctly.
Watch out for changes in the way JavaScript handles numeric types. In the future, the distinction between integers, single precision floating-point types (floats), and double precision floating-point types (doubles) may become much sharper.
In JavaScript, strings may be specified using either single quotes ('stuff') or double quotes ("otherstuff"). If you begin a string with one type of quote you must end it with that same form of quote-for example, "badstuff' is not a legal string in JavaScript. Strings may also be nested by alternating the types of quotes used. In fact, you must alternate single and double quotes if you wish to put one string inside another. Here is an example of several nested strings:
As in C and Java, JavaScript strings may contain special combinations of characters, known as escape sequences, to denote certain special characters. The rules for this are still emerging, but it is probably safe to assume that all the escape sequences defined in C will be supported. Since you will almost always be using formatting directives of HTML (such as <BR> for a line break) you will probably not use these directives very often. At the moment, the following sequences are supported:.
See the "Manipulating Text Fields" section of Chapter 6, "Interactive HTML Objects," for more information on combining HTML directives with text.
The special string "" or '' represents the zero length string. This is a perfectly valid string whose length is zero. This is the shortest JavaScript string; the length of the longest is, as usual, implementation dependent. It is reasonable to expect that most JavaScript environments will permit very long sonnets (or very short legislative measures) to be represented as single strings.
The logical, or Boolean, values true and false are typically used in expressions that test some condition to determine how to proceed. If that condition is met then one set of statements is executed; if it is not then another set is used instead. The first corresponds to the true condition, while the second represents the false condition. Not surprisingly, such expressions are known as conditional expressions. As you will see in the "Operators" section there are several comparison operators, such as the equality test (==), which result in logical values.
It is possible to think of true as 1 and false as 0. In fact, JavaScript converts these logical values into 1 and 0, respectively. JavaScript also accepts any non-zero integer in place of true, for example, so that 5 and -3 can both be used as stand-ins for true. Many different programming languages follow this same convention. It should be avoided in JavaScript, as it can lead to type confusion.
The value null has a very special role in the JavaScript language. It is the value of last resort, so to speak, for every variable. For the beginning JavaScript programmer, its primary role will be in initializing variables that do not have any more meaningful initial value. For example, in the set of variable declarations given in the "Variables and Values" section, to initialize y to some value, we should have actually written
This prevents JavaScript errors that arise when an uninitialized variable is accidentally used in an expression that requires a value. It is important to realize that the value null does not give the variable y any implicit data type. null also has the property that it may be converted to a benign form of all the other types. When it is converted to a number it becomes 0, when it is converted to a string it becomes the empty string "", and when it is converted to a Boolean value it becomes false. This is the one case where is it permissible to change the implicit data type of a variable after it is declared.
Therefore, statements such as
result in lk2 having the value "lucky" (the same as lk) and w having the value 10 (the same as x). This is why the value null is an excellent way of initializing variables-it is guaranteed to be harmless.
Several of the examples in the previous section use the + operator to combine different types of things. You may recall that when a string is combined with a number in the form
the number is converted to a string and the + operator then glues the two strings together (concatenation). However, if they are combined in the opposite order
then JavaScript attempts to convert the stringthing to a number and add it, numerically, to numberthing. If the stringthing can be converted to a string, such as "-14," then all goes well; if it cannot then an error results. This illustrates the concept of implicit conversion in JavaScript.
We have already seen that some examples of implicit conversion are completely safe. false can be converted to 0, "5" can be converted to 5, and null can be converted to just about anything. However, some conversions are obviously invalid, and others might be questionable. Questions such as, "May the string '3.0' be legitimately converted to the integer 3?" are actually very difficult to answer with complete generality.
There are two approaches to handling this complex issue: use explicit conversion whenever possible, and use implicit conversion with great care. Both approaches should be used. A detailed study of explicit conversion is in Chapter 5, "Built-In JavaScript Objects," beginning with the section, "The String Object." For the moment we will use the following rules of thumb.
See "The String Object" section of Chapter 5, "Built-In JavaScript Objects," which discusses the rules for string-to-number and number-to-string conversion, the source of most conversion errors in JavaScript.
Use implicit conversion only when converting to a string form. Never use it to convert to numerical form. This is because attempts to convert a non-numerical quantity to a numeric form cause serious JavaScript errors, while conversion to string form generally do not.
You have probably already noticed that conversion to a string is always safe, at least for the data types we have encountered so far. In fact, this type of implicit conversion is a boon to the JavaScript programmer, since it avoids the tedious formatting directives that are necessary in many languages such as C. In JavaScript we can say
without having to worry about the data type of the variable cnt. This construction will always give a valid string, and never an error.
The preceding Caution is also based on standard principles of defensive programming. There are many things that cannot be sensibly converted to a numerical form, so the prudent approach is to never try to implicitly convert anything to a number. There are several more robust approaches that can be used in case we have a string that we want to convert to numerical form. These are described in chapter 5. We will also see other exceptions to this rule as our mastery of JavaScript deepens.
The basic unit of work in JavaScript is the statement, as is the case in most programming languages. A JavaScript statement accomplishes work by causing something to be evaluated. This can be the result of giving a value to a variable, by calling a function, by performing some sort of calculation, or any combination of the. We have already seen variable declaration statements, which not only create (declare) a new variable, but also give it an initial value, such as the following statement:
JavaScript programs, as mentioned at the beginning of this chapter, are collections of statements, typically organized into functions, which manipulate variables and the HTML environment in which the script itself works, in order to achieve some goal.
Before plunging into a detailed description of the various types of statements and the operators they use, let's examine one simple statement in excruciating detail. Consider the statement
This statement contains three parts: the result y, the operator = and the expression x + 5. The result always occurs in the left side, since JavaScript always operates from left to right, and is often called the lvalue. The result must always be something that can be modified. It would be erroneous to write null = x + 5, for example, because null is a built-in, unchangeable component of JavaScript itself-it cannot be modified, so it can never appear as a result.
The operator = is the assignment operator, of course. It causes the expression on the right to be evaluated and its value given (assigned) to the result. The expression x + 5 contains another operator, the + operator, which acts to combine x and 5 in some context-specific way. Since x is a number in this case, the + operator performs ordinary addition, and y gets the value 15. As we have already seen, if x had been a string, such as "bleh", then + would have acted as a string concatenation operator and y would be given the value "bleh5" instead. This is an example of operator overloading-the + operator can do different things in different situations. Many JavaScript operators are overloaded.
There is one final point to be made about this statement, and about the structure of JavaScript programs in general. JavaScript has adopted a line oriented approach to program flow. This means that it knows that a statement has ended when it reaches the end of a line. It is also possible to explicitly terminate a statement with a semicolon character (;). The statement y = x + 5; is identical in effect to the statement y = x + 5. This also means that you can, in fact, put multiple statements on a single line by separating each of them with a semicolon.
For those just starting out in JavaScript it is often a good idea to terminate each statement with a semicolon, and to also put only a single statement on each line. This might seem both redundant and extraneous, but it is well justified. The end of a line is often a purely visual concept. Anyone who has ever used a word processor has undoubtedly encountered the situation where a very long line looks like two lines. Different platforms (Macintosh, PC, UNIX) also have their own unique ideas as to what the proper end-of-line characters are. It is much safer to put in the extra semicolon character and be explicit about the end of the statement than it is to rely on one's eyesight.
The set of operators that JavaScript uses is, once again, very similar to that of the C, C++, and Java languages. It provides a number of different ways of combining different values, both literals and variables, into expressions. Some operators require two elements to participate in the operation, and are referred to as binary operators. The + operator is a binary operator. Other operators require only a single participant (operand), and are known as unary operators. The ++ operator, which adds 1 to its operand, is a unary operator. Operators may also join forces to form aggregate operators, as we shall see next.
JavaScript operators may be classified into the following groups:
This grouping is purely functional, and is based on what the operators actually do. The next four subsections examine each type of operator in more detail. Table 2.1 summarizes the operators in each category and how they are used.
Table 2.1 A Summary of JavaScript Operations | |
---|---|
Computational Operators | |
+ | Addition, String Concatenation |
- | Subtraction, Unary Negation |
* | Multiplication |
/ | Division |
% | Modulus |
++ | Preincrement, Postincrement |
-- | Predecrement, Postdecrement |
Logical Operators | |
==, != | Equality, Inequality |
<,<=, =>, > | Arithmetic and String Comparison |
! | Logical NOT |
&&, || | Logical AND, Logical OR |
? | Conditional Selection (trinary) |
, | Logical Concatenation |
Bitwise Operators | |
&, | | Bitwise AND, Bitwise OR |
^ | Bitwise eXclusive OR (XOR) |
~ | Bitwise NOT |
<<, >>, >>> | Shift Left, Shift Right, Unsigned Shift Right |
Assignment Operators | |
= | Assignment |
OP= | Aggregate Assignment (+, -, *, /, %, &, |, ^, ~, <<, >>, >>>) |
The computational operators are addition (+), subtraction and negation (-), division (/), multiplication (*), modulus (%), increment (++), and decrement (--). These operators are often used in performing arithmetic computations, but do not forget that the + operator is overloaded; it also has the extremely important role of string concatenation.
The first five computational operators have their standard mathematical meanings. They add, subtract, divide, or multiply two numeric quantities. By combining two quantities using one of these operators the result is made as precise as possible. If an integer is added to a floating-point number, the result is a floating-point number. The following four statements illustrate the use of these operators:
Note that division of integer quantities result in an integer result, so that if w had the value 4 in the third statement, z would get the value 2, not 2.5. Note also that the - operator may also be used as a unary operator to compute the negative of a numeric quantity:
This has exactly the same effect as if we had multiplied m by -1.
The modulus operator (%) is used to compute the remainder from a division. Although it can be used with floating-point numbers, it is typically used with integers, so that 21 % 4 evaluates to 1. The modulus operator always gives a remainder that has the same sign as the corresponding quotient, so that -21 % 4 evaluates to -1, not 3.
The increment and decrement operators are conveniences created to simplify the very common operations of adding or subtracting one from a number. Both these operators are unary and come in two forms: prefix and postfix. The expression ++x is the preincrement form of the ++ operator, while x++ is the postincrement form. This leads to a subtle and often misunderstood point about the increment and decrement operators.
Supposing that x has its usual value 10, consider the two statements
These look very similar, but are in fact very different. After both of these statements have been executed, x has the value 11. However, y ends up with the value 11 while z has the value 10. Why? The reason has to do with the complex issue of what order the operators ++ and = are evaluated in these two statements. In the first statement, the ++ is evaluated first, so that x attains the value 11, and then the assignment = is evaluated, so that this value is passed on to y. In the second statement, the assignment operator = is applied first, so that z becomes 10, the current value of x, and then the ++ is applied to x, so that it advances to 11. The same rule applies to the decrement operator (--).
This might seem like it is a violation of the rule of left-to-right evaluation, and it is. Even though the equal sign is to the left of the preincrement operator (++) in the first statement, the ++ operator takes effect first. This is an example of operator precedence, the order in which multiple operators are applied. This complex topic is discussed in more detail in the "Order of Evaluation" section later in the chapter.
Logical operators in JavaScript are used either to carry out some form of test, or to combine the results of more than one such test. They are often referred to as conditional operators. The logical operators that perform a test of some sort are the equality/inequality operator (== and !=), the comparison operators (<, <=, >, and =>), and the logical negation operator (!). The operators that combine logical values are logical AND (&&) and logical OR (||). Finally, the conditional operator (?) and the comma operator (,) are also combining operators, although they are only vaguely logical operators.
The binary equality (==) and inequality (!=) operators are used to test if two quantities are the same or different. These operators are overloaded. On integers, they test for strict equality or inequality. On floating-point numbers, they test to see if the two quantities are equal within the precision of the underlying floating-point type. On strings, they test for exact equality-recall that case is significant in JavaScript strings. These operators all return a Boolean value, either true or false.
For example, if x has the value 10, y has the value 3.0, and z has the value "barney", then x == 10 is true, y != -5.0 is also true, and z == "fred" is false. Unfortunately, even operators as simple as these can be a source of error. It is regrettable that the logical operator == looks so much like the assignment operator =. Consider the following incorrect code fragment:
The purpose of this code is almost certainly to test the value of the variable x against the constant 3, and execute the "stuff" if that test succeeded. This code fails to realize that purpose in two very dramatic ways, just by inappropriately using = instead of ==.
First of all, x = 3 always gives x the value 3, no matter what its previous value was. Instead of testing x using ==, we have altered it with =. Second, the value of the expression x = 3 is the value of its left side, namely 3. Even though 3 is not a true logical value, it is treated as true by the if statement (if is described in greater detail in the section, "Control Structure", later in this chapter). This means that "stuff" will always be executed, rather than only being executed when x has the prior value 3.
This type of error occurs in every programming language in which similar operators are used for very different purposes. In this case, we could have adopted another rule of defensive programming and said
In this case, our typing mistake (= instead of ==) leads to an error, rather than resulting in a subtle programming flaw. Since 3 is a constant, it can never appear on the left side of an assignment, but it is quite capable of appearing on the left side of a logical test. Said another way, since x == 3 and 3 == x are completely equivalent, the form 3 == x is preferable. If it is mistyped as an assignment statement (3 = x) it leads to an immediate error rather than one which might take hours of debugging to uncover. This leads to the following advice.
When testing for equality always put constants on the left, especially null.
There is another subtle evil about the (in)equality operators when they are used with floating-point numbers. It is very tricky to make floating-point arithmetic completely independent of the underlying machine. This means that z == 3.0 might be true on one machine but false another. It can also lead to seemingly absurd results such as 3. != 3.00 being false while 3.0 == 2.9999999 is true. A remedy for this problem is presented at the end of this section.
The comparison operators (<, <=, > and >=) also operate on both numbers and strings. When they act on numbers they perform the usual arithmetic comparisons, yielding Boolean values, as with the equality operators. When they act on strings they perform comparisons based on dictionary order, also known as lexicographic order. If a string str1 occurs earlier in the dictionary than a second string str2 then the comparison str1 < str2 (and also str1 <= str2 ) will be true. For example, "barney" < "fred" is true, while "Franklin" < "Delano" is false.
The logical negation operator (!) is used to reverse the sense of a logical test. It converts true to false and false to true. If x < 15 is true then !(x < 15) is false, and vice versa. Note that ! may also be used with integer values, so that !0 is true, while !5 is false. As in other cases, this use of the ! operator violates type boundaries, and should be avoided.
The logical AND (&&) and OR (||) operators are among the most powerful operators in JavaScript. Both may be used to combine two or more conditions into a composite test. The logical AND of a set of conditions is true only if all of its component conditions are true. The logical OR of a set of conditions is true if any of its component conditions are true. Thus
is true precisely when x is less than 17 and the Boolean variable buttonPressed is true and z is exactly equal to the string "Meta." Similarly,
is true if one or more of the three conditions is true.
JavaScript uses a lazy variant of its usual left-to-right evaluation rule with the && and || operators. This lazy evaluation (or short circuit evaluation) rule states that JavaScript stops trying to evaluate the expression as soon as its value is known.
To see how this works, suppose that x has the value 20, buttonPressed is true, and z is the string "Hyper". Since ( x < 17 ) is false the second and third conditions in the logical AND statement are never evaluated. This is because false && anything is always false, so the value of the first expression must be false. Similarly, the second statement stops as soon as buttonPressed is evaluated. Since true || anything is always true, the second expression must be true.
Lazy evaluation can be both a boon and a curse. Suppose that "digofpi(1000000000)" is a function that computes the billionth digit of pi. The expression
does not actually try to compute the billionth digit of pi if x is 20, because the expression is already known to be true, and digofpi() is never called. As an additional example, consider the following expression:
Suppose that beaupage() is a function that displays a beautiful Web page. If x is 30 this page will never be seen, because the first part of expression ( x < 25 ) is already known to be false. As a result the function beaupage() is never called. We revisit this phenomenon in the "Functions and Objects" section at the end of this chapter. For the moment, it is wise to be aware of lazy evaluation.
The logical AND and OR operators also provide us with one solution to the problem of floating-point comparison. While it may not be possible to ever determine if x is exactly equal to the constant 3.0, you can be certain that it is close using a combined test such as
where epsilon is some suitably small value, such as 0.001. This form of test is often referred to as a fuzzy comparison.
Floating-point arithmetic is not an exact science. Avoid exact comparison test such as == and !=; use fuzzy comparisons instead.
The final two operators in the logical category are the conditional operator (?), often called the question mark operator, and the comma operator (,). These two operators are only vaguely logical, but they don't readily fall into any of the other categories either.
The conditional operator is the only trinary (3 operand) operator in JavaScript. It is used to select one of two possible alternatives based on a conditional test. The syntax for this operator is
If the conditionthing is true then the value of this expression is truealt; otherwise it is falsealt. Note that the colon (:) separating the true alternative from the false alternative is mandatory. This can be used to select an appropriate alternative and simplify code, as in this example:
This expression makes the variable printme have the string value "OK" in case the variable errorcode is 0; otherwise, it is set to "error". The question mark operator is often a fast way to select one of two choices when a control structure would be unnecessarily cumbersome.
Finally, the lowly comma operator can be used to force the evaluation of a set of expressions. All intermediate results are discarded, and the value of the very last expression on the right is returned. For example, the expression
always computes the billionth digit of pi and assigns it to the variable d, always displays the beautiful page, always compares x against 17, and only returns the result of that comparison since x < 17 is the rightmost expression. The result of that comparison is assigned to the Boolean variable b. This might seem like a clever way to outwit JavaScript's lazy evaluation, but it would be clearer to simply write
In general, the comma operator is only useful when it is inside a for loop (see "Control Structures", later in this chapter), and should otherwise be ignored.
In many situations you do not need to know, nor do you wish to know, the precise binary representation of values in your program. There are some situations, however, in which it is absolutely essential to operate at the lowest possible level and deal with the individual bits of a particular value. This often arises in mathematical applications, for example, or when precisely manipulating color values. The bitwise operators are used for this purpose. Table 2.2 shows JavaScript's bitwise operators. Note that all are binary, except for bitwise not, which is unary. Each operates on its operands one bit at a time.
Table 2.2 JavaScript's Bitwise Operators | |
---|---|
Operator Name | Symbol |
Bitwise AND | & |
Bitwise OR | | |
Bitwise XOR | ^ |
Bitwise Left Shift | << |
Bitwise Signed Right Shift | >> |
Bitwise Unsigned Right Shift | >>> |
Bitwise NOT | ~ |
Bitwise AND (&) examines each bit position in each of its operands. If both operands have a 1 bit in a given position, then that bit will also be set to 1 in the result. In all other cases, the output bit position is zero. For example, suppose x = 0x00001234 and y = 0x8000ABCD. Then z = x & y will have the value 0x00000204. You can see this more easily by writing x and y in base 2 (binary) notation, and looking for those positions in which both x and y are 1, as shown in the first part of figure 2.1.
FIG. 2.1 JavaScript's bitwise operators operate on each bit separately.
Note that x and y only have the same bits set in highlighted positions, so that those are the only bits set in their logical AND z. In this way bitwise AND is the bit level analog of the logical AND. Bitwise OR (|) is similar. If either bit is 1 in any bit position, then that bit will be 1 in the result. Thus the value of w = x | y will be 0x8000BBFD, as you see in middle part of figure 2.1.
Each bit is set in w if either or both of the corresponding bits in x and y is set. The bitwise XOR (exclusive OR) (^) operator is a variation on the bitwise OR operator. It sets a bit in the result if either bit in the operand is set, but not both. The value of v = x ^ y is 0x8000B9F9, as shown at the bottom of figure 2.1.
These three operators may also take more than two operands, so that it is possible to write a very long expression such as
which operates from left to right, as usual. This expression takes the bitwise AND of a and b, ANDs that result with c, ANDs that result with d, and finally ANDs that result with e. The final result is saved in the variable n.
The bitwise AND (&) and OR (|) operators bear a shocking similarity to their logical counterparts && and ||. This can lead to painfully undetectable errors. The same care that is exercised with = and == should also be used with these operators.
The unary bitwise NOT operator (~) changes each 0 bit in its operand to a 1 bit, and each 1 bit in its operand to a 0 bit. The bitwise NOT of x will have the value 0xFFFFEDCB:
While &, |, ^ and ~ operate on bits in place, the shift operators <<, >>, and >>> are used to move bits around. The left shift operator (<<) shifts a set of bits to the left by a specified number of positions, while both >> and >>> moves that set of bits to the right in two potentially different ways. For example, let us evaluate these three expressions:
The first of these shifts each bit in x to the left five positions. Zero bits are tacked on at the right, while the bits that are shifted out at the left are lost when they exceed the overall 32-bit length. So the value of xleft must be 0x00024680. The signed right shift operator acts in almost the same way. Each bit of y is shifted to the right three positions. Bits on the right edge of y are lost as they are shifted out. However, rather than shifting in zeros at the left side of y, the most significant bit of y, which happens to be 1 in this case, is shifted in. The resulting value of ysright is 0xF0001579.
This might seem counterintuitive, but it makes good mathematical sense, since it preserves the sign of the operand. If y is negative (most significant bit set, as in our example) then any signed right shifted version of y will also be negative. Similarly, if y had been positive (most significant bit equal to 0) then any right shifted version of y would have been positive. The unsigned right shift operator (>>>) does not preserve the sign of its operand; it always shifts 0 bits in at the left edge. The value of yusright is therefore 0x10001579. The shift processes used to compute xleft, ysright, and yusright are shown in figure 2.2.
FIG. 2.2 JavaScript's shift operators move bits to the right or left, and are equivalent to multiplication or division by a power of two.
Since all the bitwise operators act at the bit level, chaos can result if they are applied to a variable that is not an integer. Floating-point numbers are particularly sensitive, since an arbitrary bit pattern need not correspond to a valid floating-point number.
Never perform bitwise operations on floating-point numbers. Your code will be unportable, and floating-point exceptions may result.
Our tour of JavaScript operators concludes with the assignment operator and its aggregates. You have already seen many examples of that most fundamental of all operators, the assignment operator (=). You are well aware that it is used to assign the result of an expression or value on the right side of the = sign to the variable or lvalue on the left side of the = sign.
In JavaScript, as in C, C++, and Java, you can also combine the assignment operator with any of the binary computational and logical operators. The expression
is just a shorthand for the expression
where OP is any of the operators +, -, /, *, %, &, |, ^, <<, >>, or >>>. So, to add 7 to x, multiply y by 19.5, OR z with 0xAA7700, and perform an unsigned right shift of 10 bits on w you can write
These compact expressions replace the wordier versions x = x + 7; y = y * 19.5, and so forth.
In elementary school math you were probably confronted with questions such as, "What is the value of 3 + 4 * 5? Is it 23 or is it 35?" This was your first exposure to the concept of order of evaluation, or operator precedence. You probably remember that multiplication has a higher precedence than addition, so that the correct answer is 23. The same issue arises in almost every programming language with the concept of operators-which comes first?
There are two approaches to this issue. The first involves learning, or attempting to learn, the operator precedence table. The more operators there are, the more rules there must be in this table. The second approach is to simply ignore the issue completely and explicit group your expressions using parentheses. Never write 3 + 4 * 5. Always write 3 + ( 4 * 5 ) or even ( 3 + 4 ) * 5 if that is what you want.
This recommendation is very much like several others in this chapter. It trades the effort (and perhaps some readability) of using the explicit parenthesized form, against the promise that the order of evaluation will always be exactly as you wrote it. Incorrect order of evaluation is almost certainly the second most common source of programming error in JavaScript (confusing = and == is the first). For the daring, figure 2.3 shows the operator precedence table for JavaScript. For everyone else the following rule of thumb is recommended.
FIG. 2.3 Use the operator precedence table to determine the order of evaluation.
Use parentheses to explicitly specify the order of evaluation in expressions containing more than one operator.
There is one case in which no amount of parentheses will help. When using the increment (++) and decrement (--) unary operators you must simply know that preincrements and predecrements always happen before anything else.
All professional code should have comments that clearly indicate the purpose and logic behind each major section of the code. JavaScript offers two comment styles-the original comment style from C and the single line comment style from C++ and Java.
C style comments are typically used to document major functions or code blocks. Because a C comment may extend over multiple lines it is ideal for detailed discussions of important parts of the code. A C comment begins with /* and ends with */. Our aesthetically pleasing function beaupage() might begin with a thorough description of just what makes it so beautiful, as follows:
/* The function beaupage() draws a stunningly beautiful Web page by performing the following nineteen steps. ... list of the 19 steps */
By contrast, C++ style comments are most suitable for short, pithy descriptions which will fit on a single line. A C++ style comment begins with // and ends at the end of the current line. Critical variables, for example, might merit a Java style comment indicating how they will be used, as follows:
Both comment styles may be mixed freely in the same JavaScript program. However, such comments should never be nested, as this can lead to confusion. Also, the temptation to use HTML style comments (<!-- and -->) should be strongly resisted, for reasons which will become clear in the next chapter.
See the section, "The <SCRIPT> Tag," of Chapter 3, "Events and JavaScript," for more information on the relationship between HTML comments and JavaScript.
TROUBLESHOOTING
I have just written my first JavaScript program. Everything looks just fine, but the code does nothing. What is wrong? Here is the code:
Your comments are well thought out and informative. Unfortunately, your very first comment begins with /* but does not end with */. You have inadvertently typed *? instead of */, so that the comment does not end until very far down in your program. When you use C style comments always make sure that they match.
At this point, you have had just enough of the JavaScript language to declare variables, perform assignments, and do various types of arithmetic, string, and logical calculations. You are not yet able to write any meaningful code because you do not have any higher level constructions. In this section, we will consider various methods of controlling the way in which statements are executed. The next section will expose the highest level of JavaScript-its functions and objects.
There are three types of control structure in JavaScript, as follows:
These three control structures are very similar. Each is introduced by a keyword (while, for, and if, respectively) and each manipulates a block of JavaScript statements. A block is introduces by a left brace ({) and terminated with a right brace (}). There can be as many JavaScript statements between { and } as you wish, or as few. A block of code can even be empty, with nothing between the braces. In many ways, a block of statements is like a single gigantic statement. In particular, block structured constructs are often all or nothing-either the entire contents of the block are executed, or none of it is. Since blocks behave like single statements, it is also possible to put blocks inside other blocks, in a nested fashion.
As you will see, each of the three control structures has its own specific format and its own special uses, although it is often possible to achieve the same results using any of the three types, with varying degrees of elegance.
The if statement is used to conditionally execute a single block of code. It has two forms, the simple if statement and the if...else statement. The simple if statement consists of a conditional expression, known as the if test, and a block of code which is executed if that expression evaluates to a Boolean true. An example of an if statement follows:
if ( condstmt ) { zero or more statements }
The block of code within the braces is often called the if block. The conditional statement condstmt can be any expression that yields a logical value. Note that numerical expressions may also be used; 0 is construed as false and all other values are taken to be true. As stated earlier, an if statement should be considered a single statement. Code blocks are not traditionally terminated with a semicolon, although there is no harm in doing so. Listing 2.1 shows an example of a simple if statement.
Listing 2.1 The if Control Structure if ( ( x < 10 ) && ( -10 < x ) ) { // if test y = ( x * x * x ); // 1: cube of x ystr = "The cube of " + x + " is " + y; // 2: informative string }
In this example, the value of x is tested to see if it is less than 10 and also greater than -10. If the result of this test is true then the variable y is set equal to the expression x * x * x, known mathematically as the cube of x, in the statement 1 (labeled 1:). The variable ystr is then set to a string that expresses this cubic relationship between x and y, in statement 2. If x fails either of the two tests in the if test then neither of the two statements in the if block are executed.
It is easy to see even in this simple example that it is often desirable to have a contingency plan in case the if test is false. This leads to the second form of the if statement, the if...else control structure. In this form, one block of code is executed if the if test passes, and a second block is executed if it fails. The format of this type of if statement is as follows:
if ( condstmt ) { ifblock of statements } else { elseblock of statements }
In the current version of JavaScript, the placement of the braces is important. The opening brace ({)should be on the same line as the if keyword. If an else clause is present, the closing brace (}) of the if and the opening brace ({) of the else should both be on the same line as the else keyword. Other placements of the braces are allowed, but may not be understood.
In this form of the if statement, the if block is still executed if condstmt is true. However, in this case, the block of code following the else is executed if condstmt is false. Listing 2.2 shows an enhanced version of the code from listing 2.1 using the if...else form.
Listing 2.2 The if...else Control Structure if ( ( x < 10 ) && ( -10 < x ) ) { // if test y = ( x * x * x ); // 1: cube of x ystr = "The cube of " + x + " is " + y; // 2: informative string } else { // false case y = null; // 3: be paranoid; give y a value ystr = "Cannot compute the cube of " + x; // 4: explain the failure }
In this example, statements 1 and 2 are still executed if x meets both tests in the if test. If either test fails then statements 3 and 4 in the else block are executed instead. Statement 3 is another example of defensive programming. The variable y is given a value, albeit a meaningless value. This is done so that if y is used later it will be guaranteed to have some value (even if we forgot to initialize) regardless of whether the code flowed through the true part of the if (the if block) or the false part of the if (the else block).
Observe that ystr also gets a value no matter which of the two blocks is used. In the true case it has the informative string documenting the cube of x; in the false case it has a string indicating that the cube of x could not be computed. Since ystr will presumably be displayed to the user at some point, it is worthwhile to provide an error message. This is an example of parallel code design. Each conditional path modifies the same set of variables. For a simple case, such as listing 2.2, it is easy to ensure that this happens. There are only two variables, y and ystr, and we can see exactly where they are set in every case. For more complicated, nested conditional expressions, it can become almost impossible to observe every variable in every case. Parallel code design is a good goal to strive for nonetheless.
The while statement is used to execute a block of code while a certain condition is true. The format of the while statement is as follows:
while ( condstmt ) { zero of more statements }
The condition clause condstmt is evaluated as a logical expression. If it is true then the block of statements between the braces is executed. The flow of control then loops back to the top of the while statement, and condstmt is evaluated again. This process continues until the condstmt becomes false, or until some statement within the block forces it to terminate. Each pass through the block of code is called an iteration. Figure 2.4 illustrates the basic structure of a while statement.
FIG. 2.4 JavaScript's while control structure executes a block of statements conditionally.
The first fundamental difference between a while statement and an if statement is that the while block may be executed many times, while the if or else blocks are executed once at most. You might well wonder how a while statement ever terminates. The code shown in listing 2.3 illustrates a simple situation in which the while block eventually leads to the condstmt becoming false.
Listing 2.3 A while Loop Which Adds a Sequence of Numbers var x = 1; var xsum = 0; while ( x <= 10 ) { // loop until x is greater than 10 xsum += x; // add x to the running sum xsum x++; // increment x }
This code accumulates the sum of all the integers between 1 and 10, inclusive, in a variable called xsum. x starts out as 1, so that xsum initially becomes 1 as well. x is then incremented to 2 by the x++ statement. That value is then added to xsum, so that it becomes 1 + 2 = 3. This process continues until x finally becomes 11 and the x <= 10 condition is false. xsum at this point has the value 1 + 2 +...+ 9 + 10 = 55. Thus, the loop terminates. Note that it is critically important to initialize xsum to 0. If xsum is not initialized at all then the statement xsum += x, which is just shorthand for xsum = xsum + x, gives an error. If xsum is initialized to something other than 0, the final result contains that initial value, and is not just the sum of the integers from 1 through 10.
Listing 2.3 shows one way in which a while loop can terminate. Statements within the block may cause the conditional statement to become false. It could also happen that the conditional statement at the top of the while was never true, so that the statements within the block are not executed even once. If x had started with the value 20 in this example then the while test would have been immediately false, and the statements xsum += x and x++ would have never been executed. In this event, xsum would retain its initial value of 0.
There is a third way for a while loop to terminate. If the special statement break is encountered inside the while block, the loop is forced to terminate immediately. No further statements are executed and the condstmt is not retested. Execution continues with the first statement after the end of the while block. Listing 2.4 gives an example of the use of the break statement.
Listing 2.4 A while Loop with an Internal break Statement var x = 1; var xoddsum = 0; var xtmp = 0; var lastx = 0; while ( true ) { // 1: loop forever (well, almost) xtmp = xoddsum + x; // 2: compute a trial sum if ( xtmp > 100 ) // 3: if it is too large, then... break; // 4: we are done xoddsum += x; // 5: add x to the running sum xoddsum x += 2; // 6: increment x by 2 } lastx = x; // 7: save the final value of x in the variable lastx
The test clause of this while (statement 1) is true, which, you might well suspect, is always true. This means that there is no way for this loop to terminate unless it is forced to do so by a break statement. In statement 2 a temporary sum is formed in the variable xtmp. This sum is tested against the limit 100 in statement 3; if xtmp exceeds it then statement 4, the break statement is executed, and the loop terminates. If the test fails (xtmp is still less than 100) then the real sum is formed in statement 5. (Note that it would have been equivalent, and slightly more efficient, if we had written statement 5 as xoddsum = xtmp.) In statement 6, x is incremented by 2.
What does this while loop do? It keeps adding up numbers, odd numbers in fact, until the sum is less than 100. When the next sum would have exceeded 100, the if test succeeds, the break is executed, and the flow of control of the program reaches the first statement after the entire while block, namely statement 7. This statement saves the last value of x in a different variable, lastx. So this construction computes the largest sequence of odd numbers that can be added without having the sum exceed 100. You can easily determine for yourself that the value of lastx must be 21, since 1 + 3 + ... + 21 = 100 exactly, while 1 + 3 + ... + 21 + 23 = 123 > 100.
Listing 2.4 not only illustrates the use of break, it also shows two other elements worth noting. First, listing 2.4 contains a nested conditional: there is an if statement inside the while block. This sort of construct is extremely common, and many levels of nesting are not at all unusual. Second, this example has another very common but somewhat troublesome feature. Since the while test is always true, there is no way for the while to terminate unless the break statement is executed. In the preceding example, it was quite quickly. Suppose, however, that statement 6 had been incorrectly entered as x -= 2. In this case, xoddsum would be getting constantly smaller and xtmp would never exceed 100. This type of error is known as an infinite loop. Listing 2.3 is not immune either, even though it has a conditional test rather than a blanket true. If the final statement of that example had been mistyped as x--, it would never terminate either.
Naturally, infinite loops must be vigorously avoided. They will only terminate when some kind of internal error happens (such as an arithmetic overflow when something becomes too large) or as a result of user intervention. Unfortunately, there is no foolproof way to write a while statement (or a for statement, as we shall see shortly) that is guaranteed to be correct. JavaScript is no different than any other programming language in this respect. However, the following general principles will reduce the opportunity for error:
If the while ( true ) construction is used, then the logic that exercises the break statement must be correct. If this logic isn't correct then the loop will never terminate. If you restrict you use of while ( true ) you will have fewer infinite loops. The second suggestion is based on the observation that the more chances there are to exit the loop, the less likely it is that the loop will last forever. Listing 2.5 shows a modified version of listing 2.4 in which we have moved the test on the sum to the while clause itself, and have also added a very paranoid test on the number of times through the loop (the variable loopcount).
Listing 2.5 An Improved Form of Listing 2.4 var x = 1; var xoddsum = 0; var lastx = 0; var loopcount = 0; while ( ( xoddsum + x ) < 100 ) { // 1: loop while sum is < 100 xoddsum += x; // 2: add x to the sum xoddsum x += 2; // 3: increment x by 2 if ( ++loopcount > 1000 ) // 4: if we're working too late.. break; // 5: quit } lastx = x; // 6: save the final value of x in lastx
This version satisfies both rules. Of course, the test in statement 4 is completely unnecessary. The code is simple enough that we can reassure ourselves that it is correct and will not go into an infinite loop. Once you are writing slightly more complicated while loops, you will find that there are usually multiple possible error conditions that arise. Every time you test for an error you should consider using a break statement.
You will often see while ( true ) written as while ( 1 ). These are equivalent, since true has the numerical value 1, but the latter form is sloppy. The conditional portion of a while, if, or for statement should always be a true logical expression.
There is another special statement that may be used inside while loops: the continue statement. The continue statement is used to force the flow of control back to the top of the while loop. When a continue statement is seen, all statements between it and the end of the while block are skipped, and execution continues at the top of the while. Listing 2.6 shows a simple use for the continue statement.
Listing 2.6 A continue Statement Returns to the Top of a while var x = 0; var xsum = 0; var loopcount = 0; while ( loopcount++ < 100 ) { // 1: loop 100 times x++; // 2: increment x if ( ( x % 5 ) == 0 ) // 3: if x is divisible by 5 continue; // 4: skip it xsum += x; // 5: otherwise, add x to xsum }
This example adds up every number between 1 and 100 that is not divisible by 5. The numbers that are divisible by 5 are skipped by virtue of statements 3 and 4. Statement 3 computes the remainder when x is divided by 5. If that remainder is 0 then x must be evenly divisible by 5. In that case, the conditional in statement 3 is true, and statement 4 is executed. The continue statement causes execution to continue back to the top of the loop at statement 1. This means that statement 5 is not executed, so the sum always misses those values of x which are divisible by 5, and only those values.
Many programmers would write line 3 as if ( ! ( x%5 ) ). While this style is very common, it is also confusing and a potential source of error. One problem with this form is that it confuses JavaScript types by using the numerical value x%5 as if it were a logical value. This form also hides the explicit test for zero of listing 2.6. While this ! form is more compact, it is also more error prone, and should be avoided.
One striking difference between this listing and previous ones is that x is initialized to 0, not 1, and x is incremented at the top of the loop, not at the bottom. If the x++ were at the bottom, what would happen? The values 1, 2, 3, and 4 would all be gleefully added into xsum. When x reached 5, however, statement 3 would be true, the continue in statement 4 would be executed, and both xsum += x and x++ would be skipped. x would stay equal to 5 forever! Since the x++ statement is critical to the correct functioning of the loop, it must occur before the continue. If it occurs after the continue it will be skipped.
Any statement that must be executed on every pass through a loop must be placed before any continue statements.
The for statement is the most powerful and complex of the three flow control constructions in JavaScript. The primary purpose of the for statement is to iterate over a block of statements for some particular range of values. The for statement has the following format:
for ( initstmt; condstmt; updstmt ) { forblock }
The for clause, as shown, has three parts, separated by two mandatory semicolons. The initstmt is typically used to initialize a variable, although any valid statement may be used in this position. The initstmt is always executed exactly once, when the for statement is first encountered. The condstmt is a conditional test, and serves exactly the same function as in the while statement. It is tested at the top of each loop. The for statement terminates when this condition evaluates to false. The updstmt is executed at the bottom of each loop, as if it were placed immediately after the last statement in the for block. It is typically used to update the variable that is initialized by the initstmt.
Listing 2.7 shows a simple example of a for statement. In fact, the code in this listing accomplishes exactly the same task as the code in listing 2.3. Note that this code does not bother to initialize x when it is declared. This is because the initstmt part of the for loop sets it equal to 1 immediately.
Listing 2.7 Adding Up a Sequence of Numbers Using for var xsum = 0; var x; for ( x = 1; x <= 10; x++ ) { // 1: loop while x is <= 10 xsum += x; // 2: add x to xsum }
In many ways, the for statement is very much like a fancy version of the while statement. Many of the observations that were made for while also hold true for the for statement. In particular, it is possible to use the break and continue statements within a for loop. One of the advantages of a for loop is that its update statement is executed on every pass through the loop, even those passes that are cut short by a continue. The continue skips every statement in the block, but it does not cause the update statement to be skipped. The for statement may also be used unwisely, just like the while statement. If the condstmt portion of the for clause is omitted, it is as if a true conditional had been used, so that something within the for block must force looping to terminate. You will occasionally see the construction for(;;), which is identical in meaning to while ( true ). The two semicolons are mandatory.
The for statement also has some unique features that are not shared by while. The first is that variables may actually be declared and initialized within the initstmt portion. In listing 2.7 we could have dispensed with the external declaration of x, and put var x = 1; as the initialization portion of the for loop. This is often very convenient, since the loop variable (x in this case) is often used only within the loop itself, often making an external declaration pointless.
If a variable is only used inside a block of statements it should be declared at the top of that block. This clarifies your code, since it shows which sections of code use which variables (known as variable scope).
A second useful feature of the for statement is that both the initialization portion and the update portion of the for clause may contain multiple statements separated by the comma operator (,). Listing 2.8 shows another version of the code in listing 2.6, rewritten so that both x and lcnt become loop variables.
Listing 2.8 A for Loop with Multiple Initialization and Update Statements var xsum = 0; for ( var x = 1, lcnt = 0; lcnt < 100; x++, lcnt++ ) { if ( ( x % 5 ) == 0 ) // if x is divisible by 5 continue; // skip it xsum += x; // otherwise, add x to xsum }
This usage underlines the fact that both x and lcnt are used only within the body of the for loop. It is also much more compact than its counterpart in listing 2.6. In this example, we need not worry about the logical effect of the continue; we know that both x++ and lcnt++ will always be executed. This is also the most common and useful way to use the comma operator.
Finally, there is another form of the for statement that is used exclusively with objects and arrays in JavaScript: the for...in statement. We will see how this is used in Chapter 4, "JavaScript Objects." Figure 2.5 shows the basic structure of the for, while, and if statements, and the use of the break and continue statements within them.
See the section, "Objects, Methods and Properties in JavaScript," of Chapter 4, "JavaScript Objects," for a description of the for...in statement.
FIG. 2.5 Control statements determine the flow of execution in JavaScript.
The basic statements, expressions, and operators that were discussed at the beginning of this chapter are what computer scientists usually call primitives. Primitives are the building blocks from which more complex elements of a program are constructed. The for, while, and if control structures represent the next higher level of organization in JavaScript. Each of these control structures deals with blocks of code whose execution is controlled by the various conditional tests and other clauses. The for, while, and if statements are all block structured.
Functions and objects represent the highest level of organization within the JavaScript language. We will spend many chapters learning how to make effective use of these concepts. The purpose of this section is to introduce them and describe their basic features.
A function is a block of code that has a name. Whenever that name is used the function is called, which means that the code within that function is executed. Functions may also be called with values, known as parameters, which may be used inside the body of the function. Functions serve two purposes. A function is an organizational tool, in the sense that it permits you to perform the same operation without simply copying the same code.
The second purpose of JavaScript functions is to link actions on a Web page with JavaScript code. Mouse clicks, button presses, text selections, and other user actions can call JavaScript functions by including suitable tags in the HTML source for the page.
Event handler functions are explored in Chapter 3, particularly in the "Events and HTML Tags" section.
The syntax for a function statement in JavaScript is as follows:
function Name ( listofparams ) { body }
The function's Name is given immediately after the function keyword. All function names should be unique, and also should not conflict with any of the statement names which JavaScript itself uses (known as the reserved words). You cannot have a function named while, for example, and you should not have two functions both named UserHelp. The listofparams is a comma-separated list of the values that are passed into the function. These are referred to as the function's parameters, or arguments. This list may be empty, indicating that the function does not use any arguments (often called a void function). The function's body is the set of statements that make up the function. Listing 2.9 shows a function that adds up all the integers starting at 1 and ending at a value given as the sole argument.
Listing 2.9 A Summation function function summation ( endval ) { var thesum = 0; // this variable will hold the sum for ( var iter = 1; iter < endval; iter++ ) { thesum += iter; // add the integer into the sum } // end of the for loop return( thesum ); // return the sum }
This function does the same task that came up in the discussions of the while and for statements earlier in this chapter. Now that it has been written as a function, this code never needs to be repeated again. Any time you wish to form the sum 1 + 2 + ... + N, you can simply call the function, as summation(N), and it will perform the task. Notice that the endval parameter is used as the argument to the function.
When the function is called, as summation(14) for example, the actual value 14 is used for endval within the function. The function then executes the for statement, with iter < 14 as its termination condition, adding in each successive value into the variable thesum. When the for loop is done, the function executes the return statement. This causes the function to give the value inside the return statement back to the caller. This means that if we write
the variable sum14 is set to the value returned by the summation function when endval is given the value 14, namely 105. Functions can return any type of value, and are not restricted to returning integers.
There are several things to notice about this example. First of all, the variables thesum and iter, which are declared within the body of this function, are local variables. This means that they are only known within the body of this function, and are therefore completely unknown outside it. It is quite possible, even likely, that there are many functions, all of which have a local variable named iter. All these various iters are unrelated. Changing the value of one of these iters would not affect any of the others. This is why the return statement is necessary; it is the only way to communicate the work of the function back to the caller.
This same restriction applies to the parameter endval as well. The arguments to a function may not be changed within that function. We could well have written endval = 15 just before the return statement in listing 2.9. This statement would do nothing; it certainly would not change the caller's 14 into a 15. It might seem like every function would always have a return statement. This is not the case, however, since it is possible for a function to have side effects without actually returning a value. This happens by referencing external objects, which are our next topic.
Functions are used to provide a uniform method for organizing code. Objects serve the same purpose for data. Up to this point the only data items we have seen are simple variables declared with var. Each of these typeless quantities can only hold a single value of some sort at a time. Objects provide the ability to hold multiple values, so that a group of related data elements can be associated with one another.
What JavaScript calls an object is called a data structure (or class) in many other languages. As with JavaScript functions, there are two aspects to JavaScript objects: creating them and using them. For the moment we will defer the question of how to create objects and concentrate on how they are used. We will also see that a JavaScript capable browser will provide a number of its own, built-in objects.
A JavaScript object is made up of a set of component parts, which are called its properties, or members. Suppose you have an object named appt which you are using to organize your appointments. The appointment object might have properties that specify the date and time of the appointment, as well as the name of the person with whom the appointment will take place. It might also have a general description field to remind you of the purpose of this meeting. Thus, you can imagine that the appt object will have the following properties:
Each of the properties of the appt object are referenced using the dot operator (.). Thus, appt.month refers to the month property and appt.why gives us the reason for the appointment. These references may appear on both the right and left sides of an expression; we may get their values and also set them. Listing 2.10 shows a code fragment that tests the value of appt and displays a message about a current appointment.
Listing 2.10 Using the appt Object if ( appt.day == Today ) { document.write('<BR>You have an appointment today<BR>'); document.write('See ' + appt.who + ' at ' + appt.time<BR>'); document.write(appt.why + '<BR>'); }
This example assumes that the variable Today has somehow been initialized with today's date, so that the equality test with appt.day is only true for today's appointments. If the test does succeed then the three statements in the if block are executed. Each of these references document.write. The document object is a built-in object of the Netscape Navigator browser. This object has a member known as write, which is actually a function. Functional members of JavaScript objects are known as methods. This particular method takes a string and displays it on the current Web page.
Each of the three strings that are passed to document.write are constructed using + as a string concatenation operator. Each of them references one or more properties of the appt object in order to provide meaningful messages to the user. Each also includes <BR>, the HTML construction for a line break. This ability to directly issue HTML directives is one of the most powerful aspects of JavaScript, as it allows the programmer to dynamically modify the contents of Web pages using JavaScript functions and objects.
Once you learn more about the Date object, in "The Date Object" section of chapter 5, you will be able to construct a much more satisfying version of this example. Even at this stage, however, the advantage of object-based programming should be apparent. Rather than carrying about many variables, you can use objects instead. Each object can contain all the variables of interest to a particular idea. It can also contain method functions that perform related work. Objects can even contain other objects, so that you can organize your data in a hierarchical structure. Subsequent chapters explore these ideas in much greater detail.
|