Object-Oriented JavaScript: Part 3 | WebReference

Object-Oriented JavaScript: Part 3


Object-Oriented JavaScript: Part 3

By Cristian Darie, Bogdan Brinzarea

Digg This Add to del.icio.us

[This is an excerpt from the book, Microsoft AJAX Library Essentials: Client-side ASP.NET AJAX 1.0 Explained, by Cristian Darie, Bogdan Brinzarea. Published by Packt Publishing Ltd., 2007]


You learned earlier that in JavaScript you should define "class methods" outside the body of the "class", in order to prevent their multiplication for each instantiated object. Prototyping is a JavaScript language feature that allows attaching functions and properties to the "blueprint" of a function. When functions are added to a class (function) prototype, they are not replicated for each object of the class (function). This reflects quite well the behavior of classes in C#, although the core mechanism and the specific implementation details differ greatly. A few facts that you should keep in mind about prototypes are:

  • Every JavaScript function has a property named prototype. Adding members to the function's prototype is implemented by adding them to the prototype property of the function.
  • Private variables of a function aren't accessible through functions added to its prototype.
  • You can add members to a function's prototype at any time, but this won't affect objects that were already created. It will affect only any new ones.
  • You can add members to a function's prototype only after the function itself has been defined.

The Table "class" from the previous example contains a "method" named getCellCount(). The following code creates the same class, but this time adding getCellCount() to its prototype:

The JavaScript Execution Context

In this section we'll take a peek under the hood of the JavaScript closures and the mechanisms that allow us to create classes, objects, and object members in JavaScript. For most cases, understanding these mechanisms isn't absolutely necessary for writing JavaScript code—so you can skip it if it sounds too advanced. If, on the contrary, you should be interested in learning more about the JavaScript parser's inner workings, see the more advanced article online.

The JavaScript execution context is a concept that explains much of the behavior of JavaScript functions, and of the code samples presented earlier. The execution context represents the environment in which a piece of JavaScript code executes. JavaScript knows of three execution contexts:

  • The global execution context is the implicit environment (context) in which the JavaScript code that is not part of any function executes.
  • The function execution context is the context in which the code of a function executes. A function context is created automatically when a function is executed, and removed from the contexts stack afterwards.
  • The eval() execution context is the context in which JavaScript code executed using the eval() function runs.

Each execution context has an associated scope, which specifies the objects that are accessible to the code executing within that context.

The scope of the global execution context contains the locally defined variables and functions, and the browser's window object. In that context, this is equivalent to window, so you can access, for example, the location property of that object using either this.location or window.location.

The scope of a function execution context contains the function's parameters, the locally defined variables and functions, and the variables and functions in the scope of the calling code. This explains why the getCellCount() function has access to the _rows and _columns variables that are defined in the outer function (Table):

The scope of the eval() execution context is identical to the scope of the calling code context. The getCellCount() function from the above code snippet could be written like this, without losing its functionality:

var x, this.x, and x

An execution context contains a collection of (key, value) associations representing the local variables and functions, a prototype whose members can be accessed through the this keyword, a collection of function parameters (if the context was created for a function call), and information about the context of the calling code.

Members accessed through this, and those declared using var, are stored in separate places, except in the case of the global execution context where variables and properties are the same thing. In objects, variables declared through var are not accessible through function instances, which makes them perfect for implementing private "class" members, as you could see in an earlier exercise. On the other hand, members accessed through this are accessible through function instances, so we can use them to implement public members.

When a member is read using its literal name, its value is first searched for in the list of local variables. If it's not found there, it'll be searched for in the prototype. To understand the implications, see the following function, which defines a local variable x, and a property named x. If you execute the function, you'll see that the value of x is read from the local variable, even though you also have a property with the same name:

Calling this function, either directly or by creating an instance of it, will display 1 and 2—demonstrating that variables and properties are stored separately. Should you execute the same code in the global context (without a function), for which variables and properties are the same, you'd get the same value displayed twice.

When reading a member using its name literally (without this), if there's no local variable with that name, the value from the prototype (property) will be read instead, as this example demonstrates:

Using the Right Context

When working with JavaScript functions and objects, you need to make sure the code executes in the context it was intended for, otherwise you may get unpredictable results. You saw earlier that the same code can have different output when executing inside a function or in the global context.

Things get a little more complicated when using the this keyword. As you know, each function call creates a new context in which the code executes. When the context is created, the value of this is also decided:

  • When an object is created from a function, this refers to that object.
  • In the case of a simple function call, no matter if the function is defined directly in the global context or in another function or object, this refers to the global context.

The second point is particularly important. Using this in a function that is meant to be called directly, rather than instantiated as an object, is a bad programming practice, because you end up altering the global object. Take this example that demonstrates how you can overwrite a global variable from within a function:

Modifying the global object can be used to implement various coding architectures or features, but abusing of this technique can be dangerous. On the other hand, if BigTest is instantiated using the new keyword, the this keyword will refer to the new object, rather than the global object. Modifying the previous example as highlighted below, we can see the x variable of the global context remains untouched:

When creating your own code framework, you can enforce that a function's code is executed through a function instance. The little trick involves creating a new object on the spot if the function was called directly, and using that object for further processing. This allows you to ensure that a function call will not modify any members of the global context. It works like this:

The highlighted line simply checks if the this keyword refers to an instance of BigTest (the instanceof keyword is used for this). If it's not, a new BigTest instance is returned, and execution stops. The BigTest instance, however, is executed, and this time this will be a BigTest instance, so the function will continue executing in the context of that object.

This ends our little incursion into JavaScript's internals. The complete theory is more complicated than that, and it's comprehensively covered by David Flangan's JavaScript: The Definitive Guide, Fifth Edition (O'Reilly, 2006). The FAQ at http://www.jibbering.com/faq/ will also be helpful if you need to learn about the more subtle aspects of JavaScript.