11.5 The JavaScript Name SpaceWe'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
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;
i = 3;
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 ScopeWe 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); } 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]) { ... } Scope of event handlersEvent 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(). |
|