Advanced Language Features

It doesn't take much practice to be able to write some basic JavaScript.

For those of you still reading, the JavaScript core language has some pretty fancy features we've not covered yet, and with the upcoming "ECMAScript 2" due for release in late 1999, more features of the language will be standardized. If you feel the need to tackle heavier-duty scripting tasks either inside Web browsers or outside of them, then this few pages will give you the ammunition to do the job.

Just like many other modern languages, JavaScript scripts can be modeled in an "object-oriented" way. Recent innovations by Microsoft and Netscape introduce mechanisms to support generic "exception handling," "namespace management" and "regular expressions." In the following sections you'll see the language support available and some analysis of how well supported these grown-up features are.

Before jumping into these concepts, you first need to understand some JavaScript language plumbing. So here's that first.

Prototype and Scope Chains

JavaScript is an interpreted language. If it were a compiled language, every named object, property and method used in the source code could be nailed down during the compilation phase, removing all doubt as to what was what. Then, when the compiled program was run, the program or script could blindly charge ahead, confident that everything was organized perfectly in advance by the compiler. An interpreter requires a more sophisticated approach. At any time a newly named variable can spontaneously appear, and the interpreter has the job of managing and tracking it. eval() shows how unpredictable this process can be.

In this next example, just accept for now that the strings in line 1, whose contents look suspiciously like a bit of JavaScript, do in fact get turned into runnable script somehow. The question is, how does JavaScript know to display the new_price variable in this (or in any other) script:

eval('var new_pr' + 'ice = 99.95; '); // no variables – just a string
document.write(new_price);            // shows 99.95

Clearly, an interpreter can't look through the script and foresee every possible string that might contain valid JavaScript statements in advance. It must have some additional method of tracking what properties belong to what objects. This section explains the mechanisms at work.

Simplifications Revisited

JavaScript allows objects to be constructed, using constructor functions and the prototype property. In that discussion, the implications of the prototype property were glossed over to a degree. When we tackle debugging, one of the common problems discussed is that of forgetting that the "current object" within a Web browser's form element's event handler is different to the usual object, which is the window object. In that discussion, just what a "current object" might be is glossed over to a degree as well.

Recall that methods, variables and objects are all just properties of some other object, even if that other object is merely the global object. A property name is also called an identifier. When an identifier for a property (variables, objects, methods and functions are all properties too, recall) appears in a JavaScript program, two questions need answering in order to find out what it stands for:

The answers lie in the prototype mechanism in the first case, and in the scope chain mechanism in the second.

Prototype Chains

Prototype chains allow the JavaScript interpreter to determine whether a property exists for an object or not. Here is a typical object created via a JavaScript constructor:

function Thingy(name) {
  this.name = name;
}

Thingy.prototype.color = 'blue';
Thingy.prototype.shape = 'round';

var thing = new Thingy('Jack');

Consider the composition of the thing object. JavaScript objects have properties, and each property can have only one value. If more than one value is required, the property must refer to another object. But hang on a minute. The prototype property for Thingy as declared above has two properties itself – color and shape – so it must refer to another object.

In fact, typeof(Thingy.prototype) reports "object." If the prototype property is an object, it must have its own prototype, assumedly called Thingy.prototype.prototype. What about that object's own prototype? Such behavior could go on forever.

Fortunately, in the normal case the ECMAScript standard says that a constructor function's prototype property is of type Object, and the actual prototype object's own prototype property is null, so there is only one step to the end of the chain. Normally, the only places to look for a property with a given name are directly in the object, and directly in the object's prototype object.

The example above could be re-written to explicitly define the object's prototype, rather than relying on the built-in Object prototype as above. For instance:

function Thingy_common_bits() {
  this.prototype = null;
  this.colour = 'blue';
  this.shape = 'round';
}

function Thingy(name) {
  this.name = name;
}

Thingy.prototype = new Thingy_common_bits();
var thing = new Thingy('Jack');

The behavior for the thing object is the same as before. The only difference is that now the prototype property is explicitly set to a new object using an object constructor Thingy_common_bits(). This example could be further changed to show a more general case:

function Thingy_bit1() {    // prototype constructor
  this.prototype = null;
  this.colour = 'blue';
}

function Thingy_bit2() {    // prototype's prototype constructor
  this.shape = 'round';
}

function Thingy(name) {     // object constructor
  this.name = name;
}

Thingy.prototype      = new Thingy_bit1();
Thingy_bit1.prototype = new Thingy_bit2();

var thing = new Thingy('Jack');   // object

The thing object still looks the same as the other examples, but this time it is contributed to by two prototype objects, one directly (its own prototype), and one indirectly (its prototype's prototype). This is a prototype chain. If the chain is exhausted without a given property name being found, the property is undefined.

Scope Chains

Prototype chains describe how to rummage around in an object looking for a property. How does JavaScript decide which object or objects to rummage around in?

A scope chain is used by the JavaScript interpreter in order to determine which objects to examine for a given property name. Each object so determined will then have its prototype chain examined. There must always be at least one candidate object, even if it is just the global object.

A scope chain's details are not accessible to the scriptwriter. It is part of the script's "execution context," or "thread of execution," or "interpreter instance," or "housekeeping data," whichever you prefer. All those terms mean it's something present you can rely on, provided you don't go outside the familiar embrace of the interpreter. There are some under-the-hood techniques involving special properties that start with double-underscore. However, they are mostly of use to programmers who embed JavaScript interpreters in other products and languages, and this book doesn't cover that topic extensively.

According to ECMAScript, the scriptwriter can't directly interact with the scope chain. However, it is easy to change it, particularly using the with statement as this example shows:

var plant = 'Geranium';              // == window.plant

var outside = new Object;            // == window.outside
outside.furniture = 'Banana Lounge'; // == window.outside.furniture
outside.plant = 'Willow';            // == window.outside.plant

outside.inside = new Object;         // == window.outside.inside
outside.inside.plant = 'Ficus';      // == window.outside.inside.plant

alert(plant);
with ( outside ) {
  alert(plant);
  with ( inside ) {
    alert(plant);
    alert(furniture);
  }
}

Not surprisingly, the example displays alerts for Geranium, Willow and Ficus in that order. It may come as a surprise that Banana Lounge is then displayed as well, even though the inside object has no furniture property.

The reason Banana Lounge is displayed is that the with statement does not replace the object usually scrutinized when an identifier name is encountered. Instead, it adds an object to the scrutiny process.

The object added (the argument of the with statement) is the "current object" or "current scope," to use familiar terminology, but it is really just the first in a list of such objects. This list is called the scope chain. Objects in the scope chain are consulted in order until the identifier matches one of the object's properties.

In the above example, each with statement adds an object to the front of the scope chain when it starts, and removes that item when it ends. When the innermost statements are executed, the chain consists of three objects: inside, outside, and window. inside is consulted first.

The business of scope chains is a subtle source of bugs. Since it is possible to locate all the property names of all the objects in the scope chain, not just those of the object at the head of the chain, there are many more opportunities to accidentally clash with an existing name.

Finally, this extremely simple example leaps ahead a little, drawing elements on browsers using on Forms and Data. It shows that the interpreter may organize the scope chain before a piece of script even starts running, based on the context in which it runs:

<HTML><HEAD>
<SCRIPT>
  function show_name(x)
  {
    alert(x);
  }
</SCRIPT>
</HEAD><BODY>
<FORM METHOD="POST" NAME="foofoo">
  <INPUT TYPE="button" NAME="Test Name" ONCLICK="show_name(name)">
</FORM>
</BODY></HTML>

In this example, both the show_name() function and the name property are correctly identified inside the event handler (the bit of code following ONCLICK in line 8). However, show_name() is only a property of the window object, while name is a property of the window, form and element (button) objects. Since the button's name is passed to the alert box, both window and button objects must be in the scope chain, and the button object must be before the window object. Very convenient, as it saves using "window.show_name()" in the event handler code, but it does create extra opportunities for name mix-ups, as in the non-handler case.

Particularly confusing in Web browsers can be the use of identifiers such as location which exist in both the window and document objects – that is why it is wise to prefix identifiers in browsers with "window." everywhere that is practical.