[ Previous Section | Appendix Index | Main Index ]

Section A.3

The JavaScript Programming Language


JavaScript is a programming language that was created for use on Web pages. More recently, a version known as node has made it possible to use JavaScript for server-side programs, and even for general programming. JavaScript was first developed by Netscape (the predecessor of the Firefox web browser) at about the same time that Java was introduced, and the name JavaScript was chosen to ride the tide of Java's increasing popularity. In spite of the similar names, the two languages are quite different. Actually, there is no standardized language named JavaScript. The standardized language is officially called ECMAScript, but the name is not widely used in practice, and versions of JavaScript in actual web browsers don't necessarily implement the standard exactly.

Traditionally, it has been difficult for programmers to deal with the differences among JavaScript implementations in different web browsers. But almost all modern browsers now implement the features discussed in this section, which are specified by ECMAScript 6, also known as ES6. A notable exception is Internet Explorer, the predecessor of Microsoft's Edge browser, which really should no longer be used.

This page is a short overview of JavaScript. If you really want to learn JavaScript in detail, you might consider the book JavaScript: The Definitive Guide, seventh edition, by David Flanagan.


A.3.1  The Core Language

JavaScript is most closely associated with web pages, but it is a general purpose language that is used in other places too. There is a core language that has nothing to do with web pages in particular, and we begin by looking at that core.

JavaScript has a looser syntax than either Java or C. One example is the use of semicolons, which are not required at the end of a statement, unless another statement follows on the same line. Like many cases of loose syntax rules, this can lead to some unexpected bugs. If a line is a legal statement, it is considered to be a complete statement, and the next line is the start of a new statement—even if you meant the next line to be a continuation of the same statement. I have been burned by this fact with code of the form

return
       "very long string";

The "return" on the first line is a legal statement, so the value on the next line is not considered to be part of that statement. The result was a function that returned without returning a value. This also depends on the fact that JavaScript will accept any expression, such as the string on the second line, as a statement, even if evaluating the expression doesn't have any effect.

Variables in JavaScript are not typed. That is, when you declare a variable, you don't declare what type it is, and the variable can refer to data of any type. Variables are usually declared using the keyword let, and they can optionally be initialized when they are declared:

let x, y;
let name = "David";

A variable whose value will not be changed after it is initialized can be declared using const instead of var:

const name = "David";

Before ES6, variables could only be declared with the keyword var (as in "var x,y;"). It is still possible to declare variables using var, but let and const are now preferred. (One peculiarity of using var is that it is OK in JavaScript to use it to declare the same variable more than once; a declaration just says that the variable exists.)

JavaScript also allows you to use variables without declaring them. However, doing so is not a good idea. You can prevent the use of undeclared variables, as well as certain other unsafe practices, by including the following statement at the beginning of your program:

"use strict";

Although variables don't have types, values do. A value can be a number, a string, a boolean, an object, a function, or a couple more exotic things. A variable that has never been assigned a value has the special value undefined. (The fact that a function can be used as a data value might be a surprise to you; more on that later.) You can determine the type of a value, using the typeof operator: The expression typeof x returns a string that tells the type of the value of x. The string can be "undefined", "number", "string", "boolean", "object", "function", "bigint", or "symbol". (Bigints and symbols are not discussed in this section.) Note that typeof returns "object" for objects of any type, including arrays. Also, typeof null is "object".

In JavaScript, "string" is considered to be a primitive data type rather than an object type. When two strings are compared using the == or != operator, the contents of the strings are compared. There is no char type. To represent a char, use a string of length 1. Strings can be concatenated with the + operator, like in Java.

String literals can be enclosed either in double quotes or in single quotes. Starting in ES6, there is also a kind of string literal known as a "template string." A template string is enclosed in single backquote characters. When a template string includes a JavaScript expression between ${ and }, the value of that expression is inserted into the string. For example, if x is 5 and y is 12, then the statement

result = `The product of ${x} and ${y} is ${x*y}`;

assigns the string "The product of 5 and 12 is 60" to result. Furthermore, a template string can include line feeds, so they provide an easy way to make long, multiline strings. (The backquote, or backtick, key might be in the top left corner of your keyboard.)

There is not a strict distinction between integers and real numbers. Both are of type "number". In JavaScript, unlike Java and C, division of integers produces a real number, so that 1/2 in JavaScript is 0.5, not 0 as it would be in Java.

Although there is a boolean type, with literal values true and false, you can actually use any type of value in a boolean context. So, you will often see tests in JavaScript such as

if (x) { ... }

The value of x as a boolean is considered to be false if x is the number zero or is the empty string or is null or is undefined. Effectively, any type of value can be converted implicitly to boolean

In fact, JavaScript does many implicit conversions that you might not expect. For example, when comparing a number to string using the == operator, JavaScript will try to convert the string into a number. So, the value of 17 == "17" is true. The value of "" == 0 is also true, since in this case JavaScript converts both operands to boolean. Since this behavior is not always what you want, JavaScript has operators === and !== that are similar to == and != except that they never do type conversion on their operands. So, for example, 17 === "17" is false. In general, === and !== are the preferred operators for equality tests.

JavaScript will also try to convert a string to a number if you multiply, divide, or subtract a string and a number—but not if you add them, since in that case it interprets the + operator as string concatenation, and it converts the number into to a string.

JavaScript does not have type-casting as it exists in Java. However, you can use Number, String, and Boolean as conversion functions. For example,

x = Number(y);

will attempt to convert y to a number. You can apply this, for example, when y is a string. If the conversion fails, the value of x will be NaN, a special number value indicating "Not a Number." The Number function converts the empty string to zero.

Mathematical functions in JavaScript are defined in a Math object, which is similar to the Math class in Java. For example, there are functions Math.sin(x), Math.cos(x), Math.abs(x), and Math.sqrt(x). Math.PI is the mathematical constant π. Math.random() is a function that returns a random number in the range 0.0 to 1.0, including 0.0 but excluding 1.0.


JavaScript control structures are similar to those in Java or C, including if, while, for, do..while, and switch. JavaScript has a try..catch statement for handling exceptions that is similar to Java's, but since variables are untyped, there is only one catch block, and it does not declare a type for the exception. (That is, you say, "catch (e)" rather than "catch(Exception e)".) For an example, see the init() function in the sample program canvas2d/GraphicsStarter.html. An error can be generated using the throw statement. Any type of value can be thrown. You might, for example, throw a string that represents an error message:

throw "Sorry, that value is illegal.";

However, it is preferable to throw an object belonging to the class Error or one of its subclasses:

throw new Error("Sorry, that value is illegal.");

Functions in JavaScript can be defined using the function keyword. Since variables are untyped, no return type is declared and parameters do not have declared types. Here is a typical function definition:

function square(x) {
    return x * x;
}

A function can return any type of value, or it can return nothing (like a void method in Java). In fact, the same function might sometimes return a value and sometimes not, although that would not be good style. JavaScript does not require the number of parameters in a function call to match the number of parameters in the definition of the function. If you provide too few parameters in the function call, then the extra parameters in the function definition get the value undefined. You can check for this in the function by testing if typeof the parameter is "undefined". There can be a good reason for doing this: It makes it possible to have optional parameters. For example, consider

function multiple( str, count ) {
     if ( typeof count === "undefined" ) {
         count = 2;
     }
     let copies = "";
     for (let i = 0; i < count; i++) {
         copies += str;
     }
     return copies;
}

If no value is provided for count, as in the function call multiple("boo"), then count will be set to 2. Note by the way that declaring a variable in a function using let or const makes it local to the function, or more generally to the block in which it is declared. (Declaring it using var makes it local to the function but not to the block where it is declared.)

It is also possible to provide a default value for a parameter, which will be used if the function call does not include a value for that parameter or if the value that is provided is undefined. For example, the above function could also be written as

function multiple( str, count = 2 ) { // default value of count is 2
     let copies = "";
     for (let i = 0; i < count; i++) {
         copies += str;
     }
     return copies;
}

You can also provide extra values in a function call, using something called a "rest parameter": The last parameter in the parameter list can be preceded by three dots, as in "function f(x, y, ...z)". Any extra parameters are gathered into an array, which becomes the value of the rest parameter inside the function. For example, this makes it possible to write a sum function that takes any number of input values:

function sum(...rest) {
    let total = 0;
    for (let i = 0; i < rest.length; i++) {
         total += rest[i];
    }
    return total;
}

With this definition, you can call sum(2,2), sum(1,2,3,4,5), and even sum(). The value of the last function call is zero.

(An older technique for dealing with a variable number of parameters is to use the special variable arguments. In a function definition, arguments is an array-like object that contains the values of all of the parameters that were passed to the function.)

It is possible to define a function inside another function. The nested function is then local to the function in which it is nested, and can only be used inside that function. This lets you define a "helper function" inside the function that uses it, instead of adding the helper function to the global namespace.


Functions in JavaScript are "first class objects." This means that functions are treated as regular data values, and you can do the sort of things with them that you do with data: assign them to variables, store them in arrays, pass them as parameters to functions, return them from functions. In fact, it is very common to do all of these things!

When you define a function using a definition like the ones in the examples shown above, it's almost the same as assigning a function to a variable. For example, given the above definition of the function sum, you can assign sum to a variable or pass it as a parameter, and you would be assigning or passing the function. And if the value of a variable is a function, you can use the variable just as you would use the function name, to call the function. That is, if you do

let f = sum;

then you can call f(1,2,3), and it will be the same as calling sum(1,2,3). (One difference between defining a function and assigning a variable is that a function defined by a function definition can be used anywhere in the program, even before the function definition. Before it starts executing the program, the computer reads the entire program to find all the function definitions that it contains. Assignment statements, on the other hand, are executed when the computer gets to them while executing the program.)

JavaScript even has something like "function literals." That is, there is a way of writing a function data value just at the point where you need it, without giving it a name or defining it with a standard function definition. Such functions are called "anonymous functions." There are two syntaxes for anonymous functions. The older syntax looks like a function definition without a name. Here, for example, an anonymous function is created and passed as the first parameter to a function named setTimeout:

setTimeout( function () {
    alert("Time's Up!");
}, 5000 );

To do the same thing without anonymous functions would require defining a standard named function that is only going to be used once:

function alertFunc() {
    alert("Time's Up!");
}

setTimeout( alertFunc, 5000 );

The second syntax for anonymous functions, new in ES6, is the "arrow function," which takes the form <parameter list> => <definition>. For example,

() => { alert("Times Up!"); }

or

(x,y) => { return x+y; }

If there is exactly one parameter, the parentheses in the parameter list can be omitted. If there is only one statement, the braces around the definition can be omitted. And if the single statement is a return statement, then the word "return" can also be omitted. Thus, we have arrow functions such as "x => x*x". An arrow function, like any function, can be assigned to a variable, passed as a parameter, or even returned as the return value of a function. For example,

setTimeout( () => alert("Times up!"), 5000);

In C, functions can be assigned to variables and passed as parameters to functions. However, there are no anonymous functions in C. Something similar to arrow functions has been added to Java in the form of "lambda expressions."


A.3.2  Arrays and Objects

An array in JavaScript is an object, which includes several methods for working with the array. The elements in an array can be of any type; in fact, different elements in the same array can have different types. An array value can be created as a list of values enclosed between square brackets, [ and ]. For example:

let A = [ 1, 2, 3, 4, 5 ];
let B = [ "foo", "bar" ];
let C = [];

The last line in this example creates an empty array, which initially has length zero. An array can also be created using a constructor that specifies the initial size of the array:

let D = new Array(100);  // space for 100 elements

Initially, the elements of D all have the value undefined.

The length of an array is not fixed. (This makes JavaScript arrays more similar to Java ArrayLists than they are to Java or C arrays.) If A is an array, its current length is A.length. The push method can be used to add a new element to the end of an array, increasing its length by one: A.push(6). The pop method removes and returns the last item: A.pop(). In fact, it is legal to assign a value to an array element that does not yet exist:

let E = [ 1, 2, 3 ];  // E has length 3
E[100] = 17;  // E now has length 101.

In this example, when a value is assigned to E[100], the length of the array is increased to make it large enough to hold the new element.

Modern JavaScript has an alternative version of the for loop that is particularly useful with arrays. It takes the form for (let v of A) ..., where A is an array and v is the loop control variable. In the body of the loop, the loop control variable takes on the value of each element of A in turn. Thus, to add up all the values in an array of numbers, you could say:

let total = 0;
for (let num of A) {
    total = total + num; // num is one of the items in the array A.
}

Because of their flexibility, standard JavaScript arrays are not very efficient for working with arrays of numbers. Modern web browsers define typed arrays for numerical applications. For example, an array of type Int32Array can only hold values that are 32-bit integers. Typed arrays are used extensively in WebGL; they are covered in this book when they are needed.


JavaScript has objects and classes, although its classes are not exactly equivalent to those in Java or C++. For one thing, it is possible to have objects without classes. An object is essentially just a collection of key/value pairs, where a key is a name, like an instance variable or method name in Java, which has an associated value. The term "instance variable" is not usually used in JavaScript; the preferred term is "property."

The value of a property of an object can be an ordinary data value or a function (which is just another type of data value in JavaScript). It is possible to create an object as a list of key/value pairs, enclosed by { and }. For example,

let pt = { x: 17, y: 42 };

let ajaxData = {
    url: "http://some.place.org/ajax.php",
    data: 42,
    onSuccess: function () { alert("It worked!"); },
    onFailure: function (error) { alert("Sorry, it failed: " + error); }
};

With these definitions, pt is an object. It has properties pt.x, with value 17, and pt.y, with value 42. And ajaxData is another object with properties including ajaxData.url and ajaxData.onSuccess. The value of ajaxData.onSuccess is a function, created here as an anonymous function. A function that is part of an object is often referred to as a "method" of that object, so ajaxData contains two methods, onSuccess and onFailure.

Objects are open in the sense that you can add a new property to an existing object at any time just by assigning a value. For example, given the object pt defined above, you can say

pt.z = 84;

This adds z as a new property of the object, with initial value 84.

Objects can also be created using constructors. A constructor is a function that is called using the new operator to create an object. For example,

let now = new Date();

This calls the constructor Date(), which is a standard part of JavaScript. Date is a class, and "new Date()" creates an object of type Date. When called with no parameters, new Date() constructs an object that represents the current date and time.

New classes can be created using the class keyword. A class definition contains a list of function definitions, which are declared without the "function" keyword. A class definition should include a special function named "constructor" that serves as the constructor for the class. This constructor function is actually called when the new operator is used with the name of the class. In the function definition, properties of the object are referred to using the special variable this, and properties are added to the object by assigning values to them in the constructor.

class Point2D {
    constructor(x = 0,y = 0) {
           // Construct an object of type Point2D with properties x and y.
           // (The parameters x and y to the constructor have default value 0.)
        if (typeof x !== "number" || typeof y !== "number")
            throw new TypeError("The coordinates of a point must be numbers.");
        this.x = x;
        this.y = y;
    }
    move(dx,dy) {
           // Defines a move() method as a property of any Point2D object.
        this.x = this.x + dx;
        this.y = this.y + dy;
    }
}

With this definition, it is possible to create objects of type Point2D. Any such object will have properties named x and y, and a method named move(). For example:

let p = new Point2D();  // p.x and p.y are 0.
let q = new Point2D(17,42);  // q.x is 17, q.y is 42.
q.move(10,20);  // q.x is now 27, and q.y is now 62.
q.z = 1;  // We can still add new properties to q.

A new class can extend an existing class, and then becomes a "subclass" of that class. However, this option is not covered here, except for the following simple example:

class Point3D extends Point2D {
    constructor(x = 0, y = 0, z = 0) {
        if (typeof z !== "number")
            throw new TypeError("The coordinates of a point must be numbers.");
        super(x,y);  // Call the Point2D constructor; creates this.x and this.y.
        this.z = z;  // Add the property z to the object.
    }
    move(dx,dy,dz) { // Override the definition of the move() method
        super.move(dx,dy);  // Call move() from the superclass.
        if (typeof dz !== "undefined") {
            // Allows move() to still be called with just two parameters.
            this.z = this.z + dz;
        }
    }
}

For a more extensive example of classes and subclasses, see canvas2d/HierarchicalModel2D.html.


The class keyword was new in ES6, but JavaScript already had classes. However, in earlier versions of JavaScript, a class was simply defined by a constructor function, and a constructor function could be any function called with the "new" operator. Since this kind of class is still used, it is worthwhile to look at how it works.

A constructor function is written like an ordinary function; by convention, the name of a constructor function begins with an upper case letter. A constructor function defines a class whose name is the name of the function. For example, let's see how to use a constructor function instead of the class keyword to define the class Point2D:

function Point2D(x,y) {
    if ( typeof x === "number") {
        this.x = x;
    }
    else {
        this.x = 0;
    }
    if ( typeof y === "number" ) {
        this.y = y;
    }
    else {
        this.y = 0;
    }
    this.move = function(dx,dy) {
        this.x = this.x + dx;
        this.y = this.y + dy;
    }
}

When called with the new operator, as in "new Point2D(17,42)", this function creates an object that has properties x, y, and move. These properties are created by assigning values to this.x, this.y, and this.move in the constructor function. The object that is created is essentially the same as an object created using the Point2D class defined above. (One note: the move method could not be defined here using an arrow function, since the special variable "this" is not appropriately defined in the body of an arrow function.)

The definition of the move method in this example is not done in the best way possible. The problem is that every object of type Point2D gets its own copy of move. That is, the code that defines move is duplicated for each object that is created. The solution is to use something called the "prototype" of the function Point2D.

This might take us farther into the details of JavaScript than we really need to go, but here is how it works: Every object has a prototype, which is another object. Properties of the prototype are considered to be properties of the object, unless the object is given its own property of the same name. When several objects have the same prototype, those objects share the properties of the prototype. Now, when an object is created by a constructor function, the prototype of the constructor becomes the prototype of the new object that it creates. This means that properties that are added to the prototype of a constructor function are shared by all the objects that are created by that function. Thus, instead of assigning a value to this.move in the constructor function, we can do the following outside the definition of function Point2D:

Point2D.prototype.move = function(dx,dy) {
    this.x = this.x + dx;
    this.y = this.y + dy;
}

The properties of the prototype are shared by all objects of type Point2D. In this case, there is a single copy of move in the prototype, which is used by all such objects. The result is then a Point2D class that is essentially the same as the class defined using the class keyword.


A.3.3  JavaScript on Web Pages

There are three ways to include JavaScript code on web pages (that is, in HTML files). First, you can include it inside <script> elements, which have the form

<script>
    
    // ... JavaScript code goes here ...
    
</script>

You will sometimes see a type attribute in the first line, as in

<script type="text/javascript">

The attribute specifies the programming language used for the script. However, the value "text/javascript" is the default and the type attribute is not required for JavaScript scripts. (You might also see a <script> with type="module", indicating a modular JavaScript program. Modules are used in the three.js library, but are not mentioned elsewhere in this textbook. Modules were a new feature in ES6. They make it possible to break up a large program into components and control the sharing of variables between components. Modules are not used in the programs for this textbook, but they are used in the three.js 3D graphics library. They are covered briefly in the chapter on three.js.)

The second way to use JavaScript code is to put it in a separate file, usually with a name ending with ".js", and import that file into the web page. A JavaScript file can be imported using a variation of the <script> tag that has the form

<script src="filename.js"></script>

where "filename.js" should be replaced by the URL, relative or absolute, of the JavaScript file. The closing tag, </script>, is required here to mark the end of the script, even though it is not permitted to have any code inside the script element. (If you do, it will be ignored.) Importing JavaScript code from a file in this way has the same effect as typing the code from the file directly into the web page.

Script elements of either type are often included in the <head> section of an HTML file, but they actually occur at any point in the file. You can use any number of script elements on the same page. A script can include statements such as function calls and assignment statements, as well as variable and function declarations.

The third way to use JavaScript on a web page is in event handlers that can occur inside HTML elements. For example, consider

<h1 onclick="doClick()">My Web Page</h1>

Here, the onclick attribute defines an event handler that will be executed when the user clicks on the text of the <h1> element. The value of an event handler attribute such as onclick can be any JavaScript code. It can include multiple statements, separated by semicolons, and can even extend over several lines. Here, the code is "doClick()", so that clicking the <h1> element will cause the JavaScript function doClick() to be called. I should note that this is an old-fashioned way to attach an event handler to an element, and it should not be considered best style. There are alternatives that I will mention later. Nevertheless, I sometimes do things the old-fashioned way.

It is important to understand that all the JavaScript code in <script> elements, including code in imported files, is read and executed as the page is being loaded. Usually, most of the code in such scripts consists of variable initializations and the definitions of functions that are meant to be called after the page has loaded, in response to events. Furthermore, all the scripts on a page are part of the same program. For example, you can define a variable or function in one script, even in an imported file, and then use it in another script.


JavaScript for web pages has several standard functions that allow you to interact with the user using dialog boxes. The simplest of these is alert(message), which will display message to the user in a popup dialog box, with an "OK" button that the user can click to dismiss the message.

The function prompt(question) will display question in a dialog box, along with an input field where the user can enter a response. The prompt function returns the user's response as its return value. This type of dialog box comes with an "OK" button and with a "Cancel" button. If the user hits "Cancel", the return value from prompt is null. If the user hits "OK", the return value is the content of the input field (which might be the empty string).

The function confirm(question) displays question in a dialog box along with "OK" and "Cancel" buttons. The return value is true or false, depending on whether the user hits "OK" or "Cancel".

Here, for example, is a simple guessing game that uses these functions for user interaction:

alert("I will pick a number between 1 and 100.\n"
         + "Try to guess it!");
         
do {

    let number = Math.floor( 1 + 100*Math.random() );
    let guesses = 1;
    let guess = Number( prompt("What's your guess?") );
    while (guess !== number ) {
        if ( isNaN(guess) || guess < 1 || guess > 100 ) { 
            guess = Number( prompt("Please enter an integer\n" +
                              "in the range 1 to 100") );
        }
        else if (guess < number) {
            guess = Number( prompt("Too low.  Try again!") );
            guesses++;
        }
        else {
            guess = Number( prompt("Too high.  Try again!") );
            guesses++;
        }
    }
    alert("You got it in " + guesses + " guesses.");
    
} while ( confirm("Play again?") );

(This program uses Number() to convert the user's response to a number. If the response cannot be parsed as a number, then the value will be the not-a-number value NaN. The function isNaN(guess) is used to check whether the value of guess is this special not-a-number value. It's not possible to do that by saying "if (guess === NaN)" since the expression NaN === NaN evaluates to false! The same, by the way, is true of the not-a-number value in Java.)


You can try out JavaScript code in the JavaScript consoles that are available in many web browsers. In the Chrome browser, for example, you can access a console in the menu under "More Tools" / "Developer Tools", then click the "Console" tab in the developer tools. This will show the web console at the bottom of the Chrome window, with a JavaScript input prompt. The console can also be detached into a separate window. When you type a line of JavaScript and press return, it is executed, and its value is output in the console. The code is evaluated in the context of the current web page, so you can even enter commands that affect that page. The Web console also shows JavaScript errors that occur when code on the current web page is executed, and JavaScript code can write a message to the console by calling console.log(message). All this makes the console very useful for debugging. (Browser tools also include a sophisticated JavaScript program debugger.)

Other browsers have similar developer tools. For the JavaScript console in Firefox, look for "Web Developer Tools" under "Web Developer" in the menu. In the Safari browser, use "Show JavaScript Console" in the "Develop" menu (but note that the Develop menu has to be enabled in the Safari Preferences, under the "Advanced" tab). In the Edge browser, access "Developer Tools" by hitting the F12 key.

When an error occurs on a web page, you don't get any notification, except for some output in the console. So, if your script doesn't seem to be working, the first thing you should do is open the console and look for an error message. When you are doing JavaScript development, you might want to keep the console always open.


A.3.4  Interacting with the Page

JavaScript code on a web page can manipulate the content and the style of that page. It can do this because of the DOM (Document Object Model). When a web page is loaded, everything on the page is encoded into a data structure, defined by the DOM, which can be accessed from JavaScript as a collection of objects. There are several ways to get references to these objects, but I will discuss only one: document.getElementById. Any element on a web page can have an id attribute. For example:

<img src="somepicture.jpg" id="pic">

or

<h1 id="mainhead">My Page</h1>

An id should be unique on the page, so that an element is uniquely identified by its id. Any element is represented by a DOM object. If an element has an id, you can obtain a reference to the corresponding DOM object by passing the id to the function document.getElementById. For example:

let image = document.getElementById("pic");
let heading = document.getElementById("mainhead");

Once you have a DOM object, you can use it to manipulate the element that it represents. For example, the content of the element is given by the innerHTML property of the object. The value is a string containing text or HTML code. In our example, the value of heading.innerHTML is the string "My Page". Furthermore, you can assign a value to this property, and doing so will change the content of the element. For example:

heading.innerHTML = "Best Page Ever!";

This does not just change the value of the property in the object; it actually changes the text that is displayed on the web page! This will seem odd (and maybe even a little creepy) to programmers who are new to JavaScript: It's an assignment statement that has a side effect. But that's the way the DOM works. A change to the DOM data structure that represents a web page will actually modify the page and change its display in the web browser.

Some attributes of elements become properties of the objects that represent them. This is true for the src attribute of an image element, so that in our example, we could say

image.src = "anotherpicture.jpg";

This will change the source of the image element. Again, this is a "live" assignment: When the assignment statement is executed, the image on the web page changes.

For readers who know CSS, note that the DOM object for an element has a property named style that is itself an object, representing the CSS style of the object. The style object has properties such as color, backgroundColor, and fontSize representing CSS properties. By assigning values to these properties, you can change the appearance of the element on the page. For example,

heading.style.color = "red";
heading.style.fontSize = "150%";

These commands will make the text in the <h1> element red and 50% larger than usual. The value of a style property must be a string that would be a legal value for the corresponding CSS style.

Most interesting along these lines, perhaps, are properties of input elements, since they make it possible to program interaction with the user. Suppose that in the HTML source of a web page, we have

<input type="text" id="textin">

<select id="sel">
   <option value="1">Option 1</option>
   <option value="2">Option 2</option>
   <option value="3">Option 3</option>
</select>

<input type="checkbox" id="cbox">

and in JavaScript, we have

let textin = document.getElementById("textin");
let sel = document.getElmenntById("sel");
let checkbox = document.getElementById("cbox");

Then the value of the property checkbox.checked is a boolean that can be tested to determine whether the checkbox is checked or not, and the value true or false can be assigned to checkbox.checked to check or uncheck the box programmatically. The value of checkbox.disabled is a boolean that tells whether the checkbox is disabled. (The user can't change the value of a disabled checkbox.) Again, you can both test and set this value. The properties sel.disabled and textin.disabled do the same thing for the <select> menu and the text input box. The properties textin.value and sel.value represent the current values of those elements. The value of a text input is the text that is currently in the box. The value of a <select> element is the value of the currently selected option. As an example, here is complete source code for a web page that implements a guessing game using a text input box and buttons:

<!DOCTYPE html>
<html>
<head>
<title>Guessing Game</title>
<script>
    "use strict";
    let number = Math.floor( 1 + 100*Math.random() );
    let guessCount = 0;
    let guessMessage = "Your guesses so far: ";
    function guess() {
        let userNumber = Number( document.getElementById("guess").value );
        document.getElementById("guess").value = "";
        if ( isNaN(userNumber) || userNumber < 1 || userNumber > 100 ) {
            document.getElementById("question").innerHTML =
               "Bad input!<br>Try again with an integer in the range 1 to 100.";
        }
        else if (userNumber === number) {
            guessCount++;
            document.getElementById("question").innerHTML =
                "You got it in " + guessCount + " guesses. " +
                userNumber + " is correct.<br>" + 
                "I have picked another number.  Make a guess!";
            number = Math.floor( 1 + 100*Math.random() );
            guessCount = 0;
            guessMessage = "Your guesses so far: ";
            document.getElementById("message").innerHTML = "";
        }
        else if (userNumber < number) {
            guessCount++;
            document.getElementById("question").innerHTML =
                userNumber + " is too low.<br>Try again.";
            guessMessage += " " + userNumber;
            document.getElementById("message").innerHTML = guessMessage;
        }
        else {
            guessCount++;
            document.getElementById("question").innerHTML =
                userNumber + " is too high.<br>Try again.";
            guessMessage += " " + userNumber;
            document.getElementById("message").innerHTML = guessMessage;
        }
    }
</script>
</head>
<body>
    <p id="question">I will pick a number between 1 and 100.<br>
     Try to guess it.  What is your first guess?</p>
    <p><input type="text" id="guess">
       <button onclick="guess()">Make Guess</button></p>
    <p id="message"></p>
</body>
</html>

Here's one problem with some of my discussion. Suppose that a script uses the function document.getElementById to get the DOM object for some HTML element. If that script is executed before the page has finished loading, the element that it is trying to access might not yet exist. And remember that scripts are executed as the page is loading. Of course, one solution is to call document.getElementById only in functions that are executed in response to events that can only occur after the page has loaded; that's what I did in the previous example. But sometimes, you want to assign a DOM object to a global variable. Where should you do that? One possibility is to put the script at the end of the page. That will probably work. Another, more common technique is to put the assignment into a function and arrange for that function to run after the page has finished loading. When the browser has finished loading the page and building its DOM representation, it fires a load event. You can arrange for some JavaScript code to be called in response to that event. A common way of doing this is to add an onload event-handler to the <body> tag:

<body onload="init()">

This will call a function named init() when the page has loaded. That function should include any initialization code that your program needs.

You can define similar event-handlers in other elements. For example, for <input> and <select> elements, you can supply an onchange event-handler that will be executed when the user changes the value associated with the element. This allows you to respond when the user checks or unchecks a checkbox or selects a new option from a select menu.

It's possible to include an event handler for an element in the HTML tag that creates the element, as I did with the body onload event. But that's not the preferred way to set up event handling. For one thing, the mixing of JavaScript code and HTML code is often considered to be bad style. Alternatively, there are two other ways to install event handlers using the DOM. Suppose that checkbox is a DOM object representing a check box element, probably obtained by calling document.getElementById. That object has a property named onchange that represents an event-handler for the checkbox's onchange event. You can set up event handling by assigning a function to that property. If checkBoxChanged is the function that you want to call when the user checks or unchecks the box, you can use the JavaScript command:

checkbox.onchange = checkBoxChanged;

You could also use an anonymous function:

checkbox.onchange = function() { alert("Checkbox changed"); };

Note that the value of checkbox.onchange is a function, not a string of JavaScript code.

The other way to set up event handling in JavaScript is with the addEventListener function. This technique is more flexible because it allows you to set up more than one event handler for the same event. This function is a method in any DOM element object. Using it, our checkbox example becomes

checkbox.addEventListener( "change", checkBoxChanged, false );

The first parameter to addEventListener is a string that gives the name of the event. The name is the same as the name of the event attribute in HTML, with "on" stripped off the front: onchange becomes "change". The second parameter is the function that will be called when the event occurs. It can be given as the name of a function or as an anonymous function. The third parameter is harder to explain and will, for our purposes, always be false. You can remove an event listener from an element by calling element.removeEventListener with the same parameters that were used in the call to element.addEventListener. The load event is associated with a predefined object named window, so instead of attaching an event-handler for that event in the <body> tag, you could say

window.onload = init;

or

window.addEventListener("load", init, false);

Similarly, there is an onmousedown event that is defined for any element. A handler for this event can be attached to a DOM element, elem, either by assigning a function to elem.onmousedown or by calling elem.addEventListener("mousedown",handler,false). Other common events include onmouseup, onmousemove, onclick, and onkeydown. An onkeydown event handler responds when the user presses a key on the keyboard. The handler is often attached to the document object:

document.onkeydown = doKeyPressed;

An event-handler function can take a parameter that contains information about the event. For example, in an event-handler for mouse events, using evt as the name of the parameter, evt.clientX and evt.clientY give the location of the mouse in the browser window. In a handler for the onkeydown event, evt.keyCode is a numeric code for the key that was pressed.

Event handling is a complicated subject, and I have given only a short introduction here. As a first step in learning more about events in JavaScript, you might look at the HTML source code for the sample web page canvas2d/EventsStarter.html.


[ Previous Section | Appendix Index | Main Index ]