The previous section discussed the mechanics of integrating JavaScript code into an HTML file. Now we move on to discuss exactly how that integrated JavaScript code is executed by the JavaScript interpreter. The following sections explain how different forms of JavaScript code are executed. While some of this material is fairly obvious, there are a number of important details that are not so obvious.
JavaScript statements that appear between <script> and </script> tags are executed in order of appearance; when more than one script appears in a file, the scripts are executed in the order in which they appear. If a script calls document.write( ), any text passed to that method is inserted into the document immediately after the closing </script> tag and is parsed by the HTML parser when the script finishes running. The same rules apply to scripts included from separate files with the src attribute.
The detail that is not so obvious, but is nevertheless 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> section of an HTML document, none of the <body> section of the document has been defined yet. This means that the JavaScript objects that represent the contents of the document body, such as Form and Link, have not been created yet and cannot be manipulated by that code.
Your scripts should not attempt to manipulate objects that have not yet been created. 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. Some other, similar rules apply on a case-by-case basis. For example, there are properties of the Document object that may be set only from a script in the <head> section of an HTML document, before the browser has begun to parse the document content in the <body> section. Any special rules of this sort are documented in the reference page for the affected object or property in the client-side reference.
Since scripts are executed while the HTML document that contains them is being parsed and displayed, they should not take too long to run. Because scripts can create dynamic document content with document.write( ), the HTML parser must stop parsing the document whenever the JavaScript interpreter is running a script. An HTML document cannot be fully displayed until all the scripts it contains have finished executing. If a script performs some computationally intensive task that takes a long time to run, the user may become frustrated waiting for the document to be displayed. Thus, if you need to perform a lot of computation with JavaScript, you should define a function to do the computation and invoke that function from an event handler when the user requests it, rather than doing the computation when the document is first loaded.
As I noted earlier, scripts that use the src attribute to read in external JavaScript files are executed just like scripts that include their code directly in the file. 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. (Unlike embedded images, scripts cannot be downloaded in the background while the HTML parser continues to run.) 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.
Remember that defining a function is not the same as executing it. It is perfectly safe to define a function that manipulates objects that have not yet been created. Just take care that the function is not executed or invoked until the necessary variables, objects, and so on all exist. I said earlier 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 locations of the script and form. In fact, this is a common practice. Many JavaScript programs start off with a script in the <head> of the document that does nothing more than define functions that are used in the <body> of 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 ensure two things: that all functions are defined before any event handler attempts to invoke them, and that event handlers and the functions they invoke do not attempt to use objects that have not yet been defined.
Defining an event handler as the value of an onclick or another HTML attribute is much like defining a JavaScript function: the code is not immediately executed. Event-handler execution is asynchronous. Since events generally occur when the user interacts with HTML objects, there is no way to predict when an event handler will be invoked.
Event handlers share an important restriction with scripts: they should not take a long time to execute. As we've seen, scripts should run quickly because the HTML parser cannot continue parsing until the script finishes executing. Event handlers, on the other hand, should not take long to run because the user cannot interact with your program until the program has finished handling the event. If an event handler performs some time-consuming operation, it may appear to the user that the program has hung, frozen, or crashed.
If for some reason you must perform a long operation in an event handler, be sure that the user has explicitly requested that operation, and then notify him that there will be a wait. As we'll see in Chapter 13, you can notify the user by posting an alert( ) dialog box or displaying text in the browser's status line. Also, if your program requires a lot of background processing, you can schedule a function to be called repeatedly during idle time with the setTimeout( ) method.
It is important to understand that event handlers may be invoked 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 can be invoked before a document is fully loaded has two important implications. First, if your event handler invokes a function, you must be sure that the function is already defined before the handler calls it. One way to guarantee this is to define all your functions in the <head> section of an HTML document. This section of a document is always completely parsed (and any functions in it defined) before the <body> section of the document is parsed. Since all objects that define event handlers must themselves be defined in the <body> section, functions in the <head> section are guaranteed to be defined before any event handlers are invoked.
The second implication is that you must be sure that your event handler does not attempt to manipulate HTML objects that have not yet been parsed and created. An event handler can 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's user interface in such a way that event handlers refer only to previously defined objects. For example, if you define a form that uses event handlers only on the Submit and Reset buttons, you just need to place these buttons at the bottom of the form (which is where good user-interface style says they should go anyway).
In more complex programs, you may not be able to ensure that event handlers manipulate only objects defined before them, so you need to take extra care with these programs. If an event handler manipulates only objects defined within the same form, it is pretty unlikely that you'll ever have problems. When you manipulate objects in other forms or 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 yet defined if (!parent.frames[1].document) return; // Document not yet loaded in it if (!parent.frames[1].document.myform) return; // Form not yet defined if (!parent.frames[1].document.myform.name) return; // Field not yet defined parent.frames[1].document.myform.name.value = name; } </script> <input type="text" name="lastname" onchange="set_name_other_frame(this.value)"; >
In JavaScript 1.5 and later, you can omit the existence tests in the previous code if you instead use the try/catch statement to catch the exception that will be thrown if the function is invoked before the document is fully loaded.
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, 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.fullyLoaded = true;"> <form> <input type="button" value="Do It!" onclick="if (window.fullyLoaded) doit( );"> </form> </body>
The onload event handler and its partner onunload are worth a special mention in the context of the execution order of JavaScript programs. Both of these event handlers are defined in the <body> or <frameset> tag of an HTML file. (No HTML file can legally contain both of 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 subframes have loaded, any Java applets have started running, and so on. Be aware that when you are working with multiple frames, there is no guarantee of the order in which the onload event handler is invoked for the various frames, except that the handler for the parent frame is invoked after the handlers of all its child frames.
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. You can use it to undo the effects of your onload handler or other scripts in your web page. For example, if your web page opens up a secondary browser window, the onunload handler provides an opportunity to close that window when the user moves on to some other web page. The onunload handler should not run any kind of time-consuming operation, nor should it pop up a dialog box. It exists simply to perform a quick cleanup operation; running it should not slow down or impede the user's transition to a new page.
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 to which the URL refers. This may be when a user types in a JavaScript URL or, more likely, when a user follows a link, clicks on a client-side image map, or submits a form. javascript: URLs are often used as an alternative to event handlers, and as with 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.
A final topic in our investigation of how client-side JavaScript programs run is the issue of variable lifetime. We've seen that the Window object is the global object for client-side JavaScript and that all global variables are properties of the Window object. What happens to Window objects and the variables they contain when the web browser moves from one web page to another?
Whenever a new document is loaded into a window or a frame, the Window object for that window or frame is restored to its default state: any properties and functions defined by a script in the previous document are deleted, and any of the standard system properties that may have been altered or overwritten are restored. Every document begins with a "clean slate." Your scripts can rely on this -- they will not inherit a corrupted environment from the previous document. Any variables and functions your scripts define persist only until the document is replaced with a new one.
The clean slate we're discussing here is the Window object that represents the window or frame into which the document is loaded. As we've discussed, this Window object is the global object for JavaScript code in that window or frame. However, if you're working with multiple frames or multiple windows, a script in one window may refer to the Window objects that represent other windows or frames. So in addition to considering the persistence of variables and functions defined in Window objects, we must also consider the persistence of the Window object itself.
A Window object that represents a top-level browser window exists as long as that window exists. A reference to the Window object remains valid regardless of how many web pages the window loads and unloads. The Window object is valid as long as the top-level window is open.[46]
[46]A Window object may not actually be destroyed when its window is closed. If there are still references to the Window object from other windows, the object is not garbage collected. However, a reference to a window that has been closed is of very little practical use.
A Window object that represents a frame remains valid as long as that frame remains within the frame or window that contains it. For example, if frame A contains a script that has a reference to the Window object for frame B, and a new document is loaded into frame B, frame A's reference to the Window object remains valid. Any variables or functions defined in frame B's Window object will be deleted when the new document is loaded, but the Window object itself remains valid (until the containing frame or window loads a new document and overwrites both frame A and frame B).
This means that Window objects, whether they represent top-level windows or frames, are quite persistent. The lifetime of a Window object may be longer than that of the web pages it contains and displays and longer than the lifetime of the scripts contained in the web pages it displays.
Copyright © 2003 O'Reilly & Associates. All rights reserved.