JavaScript: The Definitive Guide

Previous Chapter 11
Windows and the JavaScript Name Space
Next
 

11.5 The JavaScript Name Space

We've said that the Window object is really the most central one in client-side JavaScript. This is because it is the object that defines the name space of a program. We saw earlier that every JavaScript expression implicitly refers to the current window. This includes expressions as simple as window, which is a reference to a property within the current window that happens to refer to that window itself.

But if every expression refers to the current window, then so does code like this:

var i;  // declare a variable i
i = 0;  // assign the variable a value
The assignment i = 0 is actually the same as writing

window.i = 0;

This is an important point to understand about client-side JavaScript: variables are nothing more than properties of the current window. (This is not true for local variables declared within a function, however.)

One implication of the fact that variables are properties of the current Window object is that two variables with the same name may be declared in different windows or different frames, and they will not overwrite or conflict with each other.

Another implication is that JavaScript code running in one window or frame may read and write variables declared by code in another window or frame, as long as the first window knows how to refer to the second window.[2] So, if a top-level window has two frames, and code in the first frame does the following:

parent.frames[1].i = 3;
it is equivalent to code in the second frame doing the following:

i = 3;

[2] See Chapter 20, JavaScript Security, however, for a discussion of a "security hobble" that prevents scripts from one web server from reading values from windows that contain data from other web servers.

The final implication of the equivalence between variables and window properties is that there is no such thing as a "global variable" in client-side JavaScript--i.e., there are no user-created variables that are global to Navigator as a whole, across all windows and frames. Each variable is defined only within one window.

Recall that the function keyword that defines functions declares a variable just like the var keyword does. Since functions are referred to by variables, they to are defined only within the window in which they are declared. That is, if you define a function in one window, you cannot use it in another, unless you explicitly assign the function to a variable in the other window.

Remember that constructors are also functions, so when you define a class of objects with a constructor function and an associated prototype object, that class is only defined for a single window. (See Chapter 7, Objects, for details on constructor functions and prototype objects.) This is true of predefined constructors as well as constructors you define yourself. The String constructor is available in all windows, but that is because all windows automatically are given a property that refers to this predefined constructor function. Just as each window has its own separate reference to the constructor, each window has a separate copy of the prototype object for a constructor. So if you write a new method for manipulating JavaScript strings, and make it a method of the String class by assigning it to the String.prototype object in the current window, then all strings in that window will be able to use the new method. But the new method will not be accessible to strings defined in other windows.

Bear in mind that this discussion of variables and Window object properties does not apply to variables declared within functions. These "local" variables exist only within the function body and are not accessible outside of the function. Also, note that there is one difference between variables and properties of the current window. This difference is revealed in the behavior of the for/in loop. Window properties that were created by variable declarations are not returned by the for/in loop, while "regular" properties of the Window are. See Chapter 5, Statements, for details.

Variable Scope

We saw above that top-level variables are implemented as properties of the current window or frame object. In Chapter 6, Functions, we saw that local variables in a function are implemented as transient properties of the function object itself. From these facts, we can begin to understand variable scoping in JavaScript; we can begin to see how variable names are looked up.

Suppose a function f uses the identifier x in an expression. In order to evaluate the expression, JavaScript must look up the value of this identifier. To do so, it first checks if f itself has a property named x. If so, the value of that property is used; it is an argument, local variable, or static variable assigned to the function. If f does not have a property named x, then JavaScript next checks to see if the window that f is defined in has a property named x, and, if so, it uses the value of that property. In this case x would be a top-level or "global" (to that window) variable. Note that JavaScript looks up x in the window in which f was defined, which may not be the same as the window that is executing the script that called f. This is a subtle but important difference that can arise in some circumstances.

A similar process occurs if the function f uses document.title in an expression. In order to evaluate document.title, JavaScript must first evaluate document. It does this in the same way it evaluated x. First it sees if f has a property named document. If not, it checks whether its Window object has such a property. Once it has obtained a value for document, it proceeds to look up title as a property that object--it does not check the properties of the function or window, in this case, of course. In this example, the code probably refers to the document property of the Window object, and if the function inadvertently defined a local variable named document, the document.title expression might well be evaluated incorrectly.

What we learn from these examples is that identifiers are evaluated in two scopes: the current function, and the window in which the function is defined. In Chapter 5, Statements we saw that the with statement can be used to add additional scopes. When an identifier is evaluated, it is first looked up in the scopes specified by any containing with statements. For example, if a top-level script runs the following code:

with(o) {
  document.write(x);
}
Then the identifier x is evaluated first in the scope of the object o. If no definition is found in that object's properties, then x is evaluated in the context of the current window. If the same code occurred within a function f then x would be looked up first as a property of o, then as a property of f and finally as a property of the current window.

Recall that with statements can be nested arbitrarily, creating a variable "scope" of any depth. One interesting way to use with is with a window reference:

with(parent.frames[1]) {
   ...
}
This technique allows code in one window to easily read properties of another window. Another technique that is sometimes of interest is to place the entire body of a function within the block of a with(this) statement. What this does is create a method that evaluates identifiers by looking them up first as properties of the object that it is a method of. Note, however, that such a method would find properties of its object before it found its own local variables and arguments, which is unusual behavior!

Scope of event handlers

Event handlers are scoped differently than regular functions are. Consider the onChange() event handler of a text input field named t within an HTML form named f. If this event handler wants to evaluate the identifier x, it first uses the scope of any with statements of course, and then looks at local variables and arguments, as we saw above. If the event handler were a standalone function, it would look in the scope of the containing window next and stop there. But because this function is an event handler, it next looks in the scope of the text input element t. If the property x is not defined there, it looks at the properties of the form object f. If f does not have a property named x, JavaScript next checks to see if the Document object that contains the form has a definition of this property. Finally, if no definition of x is found in any of these objects, the containing window is checked.

If all identifiers had unique names, scope would never matter. But identifiers are not always unique, and we have to pay attention to scope. One important case is the Window.open() method and the Document.open() method. If a top-level script of a regular function calls open(), JavaScript's scoping rules will find the open property of the Window object and use this method. On the other hand, if an event handler calls open(), the scoping rules are different, and JavaScript will find the definition of open in the Document object before it finds it in the Window object. The same code may work in different ways depending on its context. The moral of this particular example is to never use the open() method without explicitly specifying whether you mean document.open() or window.open(). Be similarly cautious when using location; it, too, is a property of both the Window and Document objects.

Finally, note that if an event handler doesn't call open() directly but instead calls a function that calls open(), the function does not inherit the scope of the event handler that invoked it. The function's scope would be the function itself, and then the window that contains it, so in this case, the open() method would be interpreted as the Window.open() method, not Document.open().


Previous Home Next
Window and Frame Names Book Index Window and Variable Lifetime

HTML: The Definitive Guide CGI Programming JavaScript: The Definitive Guide Programming Perl WebMaster in a Nutshell