The previous sections of this chapter have discussed the structure of JavaScript programs. This section moves on to discuss how those programs are executed by the JavaScript interpreter. Although it may seem obvious, it is important to understand how and when a web browser executes the JavaScript code embedded in various parts of an HTML file. The subsections below explain how different forms of JavaScript code are executed and also explain the implications that you must be aware of when writing JavaScript programs.
JavaScript statements that appear between <SCRIPT> and </SCRIPT> tags are executed in the order that they appear, and, when more than one script appears in a file, those scripts are executed in the order they appear. The same rules apply to scripts included from separate files with the SRC attribute. This much is obvious.
The detail that is not so obvious, but that is important to remember, is that execution of scripts occurs as part of the web browser's HTML parsing process. Thus, if a script appears in the <HEAD> of an HTML document, none of the <BODY> of the document will have been defined yet. This means that the Form, Link, and other JavaScript objects that represent the contents of the document body will not have been created yet and cannot be manipulated by that code. (We'll learn more about these objects in Chapter 12, Programming with Windows, and the chapters that follow it, and you can find complete details in the reference section of this book.)
Because JavaScript scripts are evaluated as part of the web browser's HTML parsing, the JavaScript objects that represent parts of the HTML document do not exist until they are parsed, and your scripts should not attempt to manipulate objects that haven't been created yet. For example, you can't write a script that manipulates the contents of an HTML form if the script appears before the form in the HTML file. There are some other, similar, rules that apply on a case-by-case basis. For example, there are properties of the JavaScript Document object that may be set only from a script in the <HEAD> of an HTML document, before Navigator has begun to parse the document content from the <BODY> section. Any special rules of this sort are documented in this book's reference entry for the affected object or property.
As noted above, scripts that use the SRC attribute to read in an external JavaScript file are executed just as scripts that include their code directly in the file are. What this means is that the HTML parser and the JavaScript interpreter must both stop and wait for the external JavaScript file to be downloaded--scripts cannot be downloaded in parallel as embedded images can. Downloading an external file of JavaScript code, even over a relatively fast modem connection, can cause noticeable delays in the loading and execution of a web page. Of course, once the JavaScript code is cached locally, this problem effectively disappears.
Note that scripts using the Internet Explorer FOR and EVENT tags are not executed following the rules described here--they should rightly be considered event handlers, rather than scripts, and are executed in the same way (described below) that more conventionally defined event handlers are.
In Navigator 2.0, there is a notable bug relating to execution of scripts: whenever the web browser is resized, all the scripts within it are re-interpreted.
Remember that defining a function is not the same as executing it. It is perfectly safe to define a function that manipulates variables that aren't declared yet, or objects that haven't been created yet. You simply must take care that the function is not executed or invoked until the necessary variables, objects, and so on, all exist. We said above that you can't write a script to manipulate an HTML form if the script appears before the form in the HTML file. You can, however, write a script that defines a function to manipulate the form, regardless of the relative location of the script and form. In fact, this is quite a common thing to do. Many JavaScript programs start off with a script at the beginning of the file that does nothing more than define functions that will be used elsewhere further down in the HTML file.
It is also common to write JavaScript programs that use scripts simply to define functions that are later invoked through event handlers. As we'll see in the next section, you must take care in this case to insure two things: first, that all functions are defined before any event handler attempts to invoke them. And second, that event handlers and the functions they invoke do not attempt to use objects that have not been defined yet.
As we've seen, defining an event handler creates a JavaScript function. These event-handler functions are defined as part of the HTML parsing process, but, like functions defined directly by scripts, event handlers are not executed immediately. Event handler execution is asynchronous. Since events occur, in general, when the user interacts with HTML objects, there is no way to predict when an event handler will be invoked. In fact, event handlers may be invoked even before a web page is fully loaded and parsed. This is easier to understand if you imagine a slow network connection--even a half-loaded document may display hypertext links and form elements that the user can interact with, thereby causing event handlers to be invoked before the second half of the document is loaded.
The fact that event handlers are invoked asynchronously has two important implications. First, if your event handler invokes functions, you must be sure that the functions are already defined before the handler calls them. One way to guarantee this is to define all your functions in the <HEAD> of an HTML document. This section of a document will always be completely parsed (and any functions in it defined) before the <BODY> of the document is parsed. Since all objects that define event handlers must themselves be defined in the <BODY>, functions in the <HEAD> are guaranteed to be defined before any event handlers are invoked.
The second implication of the fact that event handlers may be invoked before a document is fully loaded is that you must be sure that event handlers do not attempt to manipulate HTML objects that have not yet been parsed and created. An event handler may always safely manipulate its own object, of course, and also any objects that are defined before it in the HTML file. One strategy is simply to define your web page user interface in such a way that event handlers always refer only to objects defined before they are. For example, if you define a form that contains event handlers only on the Submit and Reset buttons, then you simply need to place these buttons at the bottom of the form (which is where good UI style says they should go anyway).
In more complex programs, you may not be able to ensure that event handlers will only manipulate objects defined before them, and in these programs you need to take extra care. If an event handler only manipulates objects defined within the same form, it is pretty unlikely that you'll ever have problems. When you start manipulating objects in other forms or in other frames, however, this starts to be a real concern. One technique is to test for the existence of the object you want to manipulate before you manipulate it. You can do this simply by comparing it (and any parent objects) to null. For example:
<SCRIPT> function set_name_other_frame(name) { if (parent.frames[1] == null) return; // other frame not defined yet if (parent.frames[1].document) return; // document not loaded in it yet if (!parent.frames[1].document.myform) return; // form not defined yet if (!parent.frames[1].document.myform.lastname) return; // field not defined parent.frames[1].document.myform.name.value = name; } </SCRIPT> <INPUT TYPE="text" NAME="lastname" onChange="set_name_other_frame(this.value)"; >
Another technique that an event handler can use to ensure that all required objects are defined involves the onLoad() event handler. This event handler is defined in the <BODY> or <FRAMESET> tag of an HTML file and is invoked when the document or frameset is fully loaded. If you set a flag within the onLoad() event handler, then other event handlers can test this flag to see if they can safely run, with the knowledge that the document is fully loaded and all objects it contains are defined. For example:
<BODY onLoad="window.loaded = true;"> <FORM> <INPUT TYPE="button" VALUE="Press Me" onClick="if (window.loaded != true) return; doit();" > </FORM> </BODY>
Unfortunately, in Navigator 2.0, documents that contain images and do not contain frames may invoke the onLoad() handler early, and so this technique is not foolproof. A possible solution is to include a small script at the very end of the document and have this script set the necessary flag:
<SCRIPT>window.loaded = true;</SCRIPT> </BODY> </HTML>
The onLoad() event handler and its partner the onUnload() handler are worth a special mention in the context of execution order of JavaScript programs. Both these event handlers are defined in the <BODY> or <FRAMESET> tag of an HTML file. (No HTML file can legally contain both these tags.) The onLoad() handler is executed when the document or frameset is fully loaded, which means that all images have been downloaded and displayed, all sub-frames have loaded, any Java applets and plug-ins (Navigator) have started running, and so on. The onUnload() handler is executed just before the page is "unloaded", which occurs when the browser is about to move on to a new page. Be aware that when you are working with multiple frames, there is no guarantee of the order in which the onLoad() event handler will be invoked for the various frames, except that the handler for the parent frame will be invoked after the handlers of all its children frames (although this is buggy and doesn't always work correctly in Navigator 2.0).
The onLoad() event handler lets you perform initialization for your web page. And the onUnload() event handler lets you undo any lingering effects of the initialization, or perform any other necessary "clean up" on your page. For example, onLoad() could set the Window.defaultStatus property to display a special message in the browser's status bar. Then the onUnload() handler would restore the defaultStatus property to its default (the empty string) so that the message does not persist on other pages.
JavaScript code in a javascript: URL is not executed when the document containing the URL is loaded. It is not interpreted until the browser tries to "load the document" that the URL refers to. This may be when a user types in a JavaScript URL, or, more likely, it is when the user follows a link, clicks on a client-side image map, or submits a form. javascript: URLs are usually equivalent to event handlers, and like event handlers, the code in those URLs can be executed before a document is fully loaded. Thus, you must take the same precautions with javascript: URLs that you take with event handlers to ensure that they do not attempt to reference objects (or functions) that are not yet defined.
Since JavaScript entities are used as the value of HTML attributes, these pieces of JavaScript code are executed during the process of HTML parsing that is done while the document is loading. In fact, since the JavaScript code in an entity produces a value that becomes part of the HTML itself, the HTML parsing process is dependent on the JavaScript interpreter in this case. JavaScript entities can always be replaced by more cumbersome scripts that write the affected HTML tags dynamically. For example, the following line of HTML:
<INPUT TYPE="text" NAME="lastname" VALUE="&{defaults.lastname};">
<SCRIPT> document.write('<INPUT TYPE="text" NAME="lastname" VALUE="' + defaults.lastname + '">'); </SCRIPT>