49
scripts) is run. Scripts generally (but not always; see §13.3.1) run in the order in which
they appear in the document. The JavaScript code within any single script is run from
top to bottom, in the order that it appears, subject, of course, to JavaScript’s condi-
tionals, loops, and other control statements.
Once the document is loaded and all scripts have run, JavaScript execution enters its
second phase. This phase is asynchronous and event-driven. During this event-driven
phase, the web browser invokes event handler functions (defined by HTML event han-
dler attributes, by scripts executed in the first phase, or by previously invoked event
handlers) in response to events that occur asynchronously. Event handlers are most
commonly invoked in response to user input (mouse clicks, keystrokes, etc.) but may
also be triggered by network activity, elapsed time, or errors in JavaScript code. Events
and event handlers are described in detail in Chapter 17. We’ll also have more to say
about them in §13.3.2. Note that
javascript:
URLs embedded in a web page can be
thought of as a type of event handler, since they have no effect until activated by a user
input event such as clicking on a link or submitting a form.
One of the first events that occurs during the event-driven phase is the load event, which
indicates that the document is fully loaded and ready to be manipulated. JavaScript
programs often use this event as a trigger or starting signal. It is common to see programs
whose scripts define functions but take no action other than defining an
onload
event
handler function to be triggered by the load event at the beginning of the event-driven
phase of execution. It is this onload handler that then manipulates the document and
does whatever it is that the program is supposed to do. The loading phase of a JavaScript
program is relatively short, typically lasting only a second or two. Once the document
is loaded, the event-driven phase lasts for as long as the document is displayed by the
web browser. Because this phase is asynchronous and event-driven, there may be long
periods of inactivity, where no JavaScript is executed, punctuated by bursts of activity
triggered by user or network events. §13.3.4 covers the two phases of JavaScript exe-
cution in more detail.
Both core JavaScript and client-side JavaScript have a single-threaded execution model.
Scripts and event handlers are (or must appear to be) executed one at a time without
concurrency. This keeps JavaScript programming simple and is discussed in §13.3.3.
13.3.1 Synchronous, Asynchronous, and Deferred Scripts
When JavaScript was first added to web browsers, there was no API for traversing and
manipulating the structure and content of a document. The only way that JavaScript
code could affect the content of a document was to generate that content on the fly
while the document was loading. It did this using the
document.write()
method. Ex-
ample 13-3 shows what state-of-the-art JavaScript code looked like in 1996.
Example 13-3. Generating document content at load time
<h1>Table of Factorials</h1>
<script>
function factorial(n) { // A function to compute factorials
318 | Chapter 13: JavaScript in Web Browsers
Download from Wow! eBook <www.wowebook.com>
84
if (n <= 1) return n;
else return n*factorial(n-1);
}
document.write("<table>"); // Begin an HTML table
document.write("<tr><th>n</th><th>n!</th></tr>"); // Output table header
for(var i = 1; i <= 10; i++) { // Output 10 rows
document.write("<tr><td>" + i + "</td><td>" + factorial(i) + "</td></tr>");
}
document.write("</table>"); // End the table
document.write("Generated at " + new Date()); // Output a timestamp
</script>
When a script passes text to
document.write()
, that text is added to the document input
stream, and the HTML parser behaves as if the script element had been replaced by
that text. The use of
document.write()
is no longer considered good style, but it is still
possible (see §15.10.2) and this fact has an important implication. When the HTML
parser encounters a
<script>
element, it must, by default, run the script before it can
resume parsing and rendering the document. This is not much of a problem for inline
scripts, but if the script source code is in an external file specified with a
src
attribute,
this means that the portions of the document that follow the script will not appear in
the browser until the script has been downloaded and executed.
This synchronous or blocking script execution is the default only. The
<script>
tag can
have
defer
and
async
attributes, which (in browsers that support them) cause scripts
to be executed differently. These are boolean attributes—they don’t have a value; they
just need to be present on the
<script>
tag. HTML5 says that these attributes are only
meaningful when used in conjunction with the
src
attribute, but some browsers may
support deferred inline scripts as well:
<script defer src="deferred.js"></script>
<script async src="async.js"></script>
Both the
defer
and
async
attributes are ways of telling the browser that the linked script
does not use
document.write()
and won’t be generating document content, and that
therefore the browser can continue to parse and render the document while down-
loading the script. The
defer
attribute causes the browser to defer execution of the
script until after the document has been loaded and parsed and is ready to be manip-
ulated. The
async
attribute causes the browser to run the script as soon as possible but
not to block document parsing while the script is being downloaded. If a
<script>
tag
has both attributes, a browser that supports both will honor the
async
attribute and
ignore the
defer
attribute.
Note that deferred scripts run in the order in which they appear in the document. Async
scripts run as they load, which means that they may execute out of order.
At the time of this writing, the
async
and
defer
attributes are not yet widely imple-
mented, and they should be considered optimization hints only: your web pages should
be designed to work correctly even if deferred and asynchronous scripts are executed
synchronously.
13.3 Execution of JavaScript Programs | 319
Client-Side
JavaScript
52
You can load and execute scripts asynchronously, even in browsers that do not support
the
async
attribute, by dynamically creating a
<script>
element and inserting it into the
document. The
loadasync()
function shown in Example 13-4 does this. The techniques
it uses are explained in Chapter 15.
Example 13-4. Asynchronously loading and executing a script
// Asynchronously load and execute a script from a specified URL
function loadasync(url) {
var head = document.getElementsByTagName("head")[0]; // Find document <head>
var s = document.createElement("script"); // Create a <script> element
s.src = url; // Set its src attribute
head.appendChild(s); // Insert the <script> into head
}
Notice that this
loadasync()
function loads scripts dynamically—scripts that are nei-
ther included inline within the web page nor referenced statically from the web page
are loaded into the document and become part of the running JavaScript program.
13.3.2 Event-Driven JavaScript
The ancient JavaScript program shown in Example 13-3 is a synchronous one: it starts
running when the page loads, produces some output, and then terminates. This kind
of program is very uncommon today. Instead, we write programs that register event
handler functions. These functions are then invoked asynchronously when the events
for which they were registered occur. A web application that wants to enable keyboard
shortcuts for common actions would register an event handler for key events, for ex-
ample. Even noninteractive programs use events. Suppose you wanted to write a pro-
gram that would analyze the structure of its document and automatically generate a
table of contents for the document. No event handlers for user input events are neces-
sary, but the program would still register an
onload
event handler so that it would know
when the document had finished loading and was ready to have a table of contents
generated.
Events and event handling are the subject of Chapter 17, but this section will provide
a quick overview. Events have a name, such as “click”, “change”, “load”, “mouseover”,
“keypress”, or “readystatechange”, that indicates the general type of event that has
occurred. Events also have a target, which is the object on which they occurred. When
we speak of an event, we must specify both the event type (the name) and the target: a
click event on an HTMLButtonElement object, for example, or a readystatechange
event on an XMLHttpRequest object.
If we want our program to respond to an event, we write a function known as an “event
handler,” “event listener,” or sometimes just a “callback.” We then register this func-
tion so that it is invoked when the event occurs. As noted earlier, this can be done using
HTML attributes, but this kind of mixing of JavaScript code with HTML content is
discouraged. Instead, the simplest way to register an event handler is usually to assign
a JavaScript function to a property of the target object, with code like this:
320 | Chapter 13: JavaScript in Web Browsers
57
window.onload = function() { ... };
document.getElementById("button1").onclick = function() { ... };
function handleResponse() { ... }
request.onreadystatechange = handleResponse;
Notice that event handler properties have names that, by convention, begin with “on”
and are followed by the name of the event. Also notice that there are no function in-
vocations in any of the code above: we’re assigning functions themselves to these prop-
erties. The browser will perform the invocation when the events occur. Asynchronous
programming with events often involves nested functions and it is not uncommon to
end up writing code that defines functions within functions within functions.
In most browsers, for most kinds of events, event handlers are passed an object as an
argument, and the properties of this object provide details about the event. The object
passed to a click event, for example, would have a property that specified which mouse
button was clicked. (In IE, these event details are stored in the global
event
object
instead of being passed to the handler function.) The return value of an event handler
is sometimes used to indicate whether the function has sufficiently handled the event
and to prevent the browser from performing whatever default action it would otherwise
take.
Events whose targets are elements in a document often propagate up the document
tree in a process known as “bubbling.” If the user clicks the mouse on a
<button>
element, for example, a click event is fired on the button. If that event is not handled
(and its propagation stopped) by a function registered on the button, the event bubbles
up to whatever element the button is nested within, and any click event handler regis-
tered on that container element will be invoked.
If you need to register more than one event handler function for a single event, or if you
want to write a module of code that can safely register event handlers even if another
module has already registered a handler for the same event on the same target, you have
to use another event handler registration technique. Most objects that can be event
targets have a method named
addEventListener()
, which allows the registration of
multiple listeners:
window.addEventListener("load", function() {...}, false);
request.addEventListener("readystatechange", function() {...}, false);
Note that the first argument to this function is the name of the event. Although
addEventListener()
has been standardized for over a decade, Microsoft is only now
implementing it for IE9. In IE8 and earlier, you must use a similar method, named
attachEvent()
:
window.attachEvent("onload", function() {...});
See Chapter 17 for more on
addEventListener()
and
attachEvent()
.
Client-side JavaScript programs also use other kinds of asynchronous notification that
are not, technically speaking, events. If you set the
onerror
property of the Window
object to a function, that function will be invoked when a JavaScript error (or any
13.3 Execution of JavaScript Programs | 321
Client-Side
JavaScript
62
uncaught exception) occurs (see §14.6). Also, the
setTimeout()
and
setInterval()
functions (these are methods of the Window object and therefore global functions of
client-side JavaScript) trigger the invocation of a specified function after a specified
amount of time. The functions passed to
setTimeout()
are registered differently than
true event handlers, and they are usually called “callbacks” instead of “handlers,” but
they are asynchronous just as event handlers are. See §14.1 for more on
setTimeout()
and
setInterval()
.
Example 13-5 demonstrates
setTimeout()
,
addEventListener()
, and
attachEvent()
to
define an
onLoad()
function that registers a function to be run when the document
finishes loading.
onLoad()
is a very useful function, and we’ll use it in examples
throughout the rest of this book.
Example 13-5. onLoad(): invoke a function when the document loads
// Register the function f to run when the document finishes loading.
// If the document has already loaded, run it asynchronously ASAP.
function onLoad(f) {
if (onLoad.loaded) // If document is already loaded
window.setTimeout(f, 0); // Queue f to be run as soon as possible
else if (window.addEventListener) // Standard event registration method
window.addEventListener("load", f, false);
else if (window.attachEvent) // IE8 and earlier use this instead
window.attachEvent("onload", f);
}
// Start by setting a flag that indicates that the document is not loaded yet.
onLoad.loaded = false;
// And register a function to set the flag when the document does load.
onLoad(function() { onLoad.loaded = true; });
13.3.3 Client-Side JavaScript Threading Model
The core JavaScript language does not contain any threading mechanism, and client-
side JavaScript has traditionally not defined any either. HTML5 defines “WebWorkers”
which serve as a kind of a background thread (more on web workers follows), but client-
side JavaScript still behaves as if it is strictly single-threaded. Even when concurrent
execution is possible, client-side JavaScript cannot ever detect the fact that it is
occurring.
Single-threaded execution makes for much simpler scripting: you can write code with
the assurance that two event handlers will never run at the same time. You can ma-
nipulate document content knowing that no other thread is attempting to modify it at
the same time, and you never need to worry about locks, deadlock, or race conditions
when writing JavaScript code.
Single-threaded execution means that web browsers must stop responding to user input
while scripts and event handlers are executing. This places a burden on JavaScript
programmers: it means that JavaScript scripts and event handlers must not run for too
long. If a script performs a computationally intensive task, it will introduce a delay into
document loading, and the user will not see the document content until the script
322 | Chapter 13: JavaScript in Web Browsers
Documents you may be interested
Documents you may be interested