Javascript: The Definitive Guide

Previous Chapter 7 Next
 

7.5 Classes in JavaScript

Although JavaScript supports a data type we call an "object", the language's lack of strong typing and a formal inheritance mechanism mean that it is not a truly object-oriented language. Still, JavaScript does a good job of simulating the features of object-oriented languages like Java and C++. For example, we've been using the term "class" in the last few sections of this chapter, despite the fact that JavaScript does not officially define or support classes. This section will explore some of the parallels between JavaScript and the true object-oriented features of Java and C++.

We start by defining some basic terminology. An object, as we've already seen, is a data structure that "contains" various pieces of named data, and may also contain various methods to operate on those pieces of data. An object groups related data values and methods into a single convenient package, which generally makes programming easier, by increasing the modularity and reusability of code. Objects in JavaScript may have any number of properties, and properties may be added to an object dynamically. This is not the case in strictly typed languages like Java and C++--in those languages, each object has a predefined set of properties, (or fields, as they are often called) and each property contains a value of a predefined type. So when we are using JavaScript objects to simulate object-oriented programming techniques, we will generally define in advance the set of properties that each object will have, and the type of data that each property will hold.

In Java and C++, a class is thing that defines the structure of an object. It is the class that specifies exactly what fields an object contains, and what types of data each holds. It is also the class that defines the methods that operate on an object. JavaScript does not have a formal notion of a class, but, as we've seen, it approximates classes with its constructors. A constructor function can create a standard set of properties for each object it initializes. Similarly, in Navigator 3.0, the prototype object associated with the constructor can define the methods and constants that will be shared by each object initialized by the constructor.

In both JavaScript and true object-oriented languages, there may be multiple objects of the same class. We often say that an object is an instance of its class. Thus, there may be many instances of any class. Sometimes we use the term instantiate to describe the process of creating an object (an instance of a class).

In Java, it is a common programming convention to name classes with an initial capital letter, and to name objects with lower case letters. This helps to keep classes and objects distinct from each other in our code, and this is a useful convention to follow in JavaScript programming as well. In previous sections, for example, we've defined the Circle and Rectangle "classes," for example, and have created instances of those classes named c and rect.

The fields defined by a Java class may be of four basic types: "instance" variables, "instance" methods, "static" or "class" variables, and "static" or "class" methods. The paragraphs below explain the differences between these types of fields, and show how they are simulated in JavaScript.

An "instance variable" is a variable of an instance, or object. It is a variable contained in an object. Each object has its own separate copy of this variable; if there are ten objects of a given class, then there are ten copies of this variable. In our Circle class, for example, every circle object has a r property that specifies the radius of the circle. In this case r is an instance variable. Since each object has its own copy of instance variables, these variables are accessed through individual objects. If c is an object that is an instance of the Circle class, for example, then we refer to its radius as:

c.r
By default, any object property in JavaScript is an instance variable, but to truly simulate object-oriented programming, we will say that instance variables in JavaScript are those properties that are created and/or initialize in an object by the constructor function.

An "instance method" is much like an "instance variable" except that it is a method rather than a data value. (In Java, functions and methods are not data types, as they are in JavaScript, so this distinction is more clear). Instance methods are invoked on a particular "instance" or object. The area() method of our Circle class is an instance method. It is invoked on a Circle object c like this:

a = c.area();
Instance methods use the this keyword to refer to the object or instance they are operating on. An instance method can be invoked for any instance of a class, but this does not mean that each object contains its own private copy of the method, as it does its instance variables. Instead, each instance method is shared by all instances of a class. In JavaScript, we define an instance method for a class by setting a property in the constructor's prototype object to a function value. This way, all objects created by that constructor share a reference to the function, and can invoke it using the method invocation syntax shown above. (Prior to Navigator 3.0, instance methods can be defined in a constructor function, as instance variables are; this is less efficient, though.)

A "class" or "static" variable in Java is a variable that is associated with a class itself, rather than with each instance of a class. No matter how many instances of the class are created, there is only one copy of each class variable. Just as instance variables are accessed through an instance of a class, class variables are accessed through the class itself. Number.MAX_VALUE is an example of a class variable in JavaScript--the MAX_VALUE property is accessed through the Number class. Because there is only one copy of each class variable, class variables are essentially global variables. What is nice about them, however, is that by being associated with a class, they have a logical niche, a position in the JavaScript name space, where they are not likely to be overwritten by other variables with the same name. As is probably clear, we simulate a class variable in JavaScript simply by defining a property of the constructor function itself. For example, to create a class variable Circle.PI to store the mathematical constant, often used with circles, we could do the following:

Circle.PI = 3.14;

Finally, we come to class methods. A "class" or "static" method is a method associated with a class rather than with an instance of a class. Class methods are invoked through the class, rather than through a particular instance of the class. Math.sqrt(), Math.sin(), and other methods of the Math object are class methods. Because class methods are not invoked through a particular object, they cannot use the this keyword--this refers to the object that an instance method is invoked for. Like class variables, class methods are "global." Because they do not operate on a particular object, static methods can often more easily be thought of as functions that happen to be invoked through a class. Again, associating these functions with a class gives them a convenient niche in the JavaScript name space, and prevents "name space collisions" from occurring in case some other class happens to define a function with the same name. To define a class method in JavaScript, we simply set the appropriate function as a property of the constructor.

Example 7-5 is a re-implementation of our Circle class that contains examples of each of these four basic types of fields.

Example 7-5: Defining Instance/Class Variables and Methods

function Circle(radius) {   // the constructor defines the class itself
    // r is an instance variable; defined and initialized in the constructor
    this.r = radius;    
}
// Circle.PI is a class variable--it is a property of the constructor function
Circle.PI = 3.14159;
// Here is a function that computes a circle area.
function Circle_area() { return Circle.PI * this.r * this.r; }
// Here we make the function into an instance method by assigning it
// to the prototype object of the constructor. Remember that we have to
// create and discard one object before the prototype object exists
new Circle(0);
Circle.prototype.area = Circle_area;
// Here's another function. It takes two circle objects are arguments and
// returns the one that is larger (has the larger radius). 
function Circle_max(a,b) {
    if (a.r > b.r) return a;
    else return b;
}
// Since this function compares two circle objects, it doesn't make sense as 
// an instance method operating on a single circle object. But we don't want
// it to be a standalone function either, so we make it into a class method
// by assigning it to the constructor function:
Circle.max = Circle_max;
// Here is some code that uses each of these fields:
c = new Circle(1.0);       // create an instance of the Circle class
c.r = 2.2;                 // set the r instance variable
a = c.area();              // invoke the area() instance method 
x = Math.exp(Circle.PI);   // use the PI class variable in our own computation.
d = new Circle(1.2);       // create another Circle instance
bigger = Circle.max(c,d);  // use the max() class method.


Previous Home Next
Object Prototypes Book Index Objects as Associative Arrays