Section 9.3. Simulating Classes in JavaScript


9.3. Simulating Classes in JavaScript

Although JavaScript supports a datatype called an object, it does not have a formal notion of a class. This makes it quite different from classic object-oriented languages such as C++ and Java. The common conception about object-oriented programming languages is that they are strongly typed and support class-based inheritance. By these criteria, it is easy to dismiss JavaScript as not being a true object-oriented language. On the other hand, you've seen that JavaScript makes heavy use of objects, and it has its own type of prototype-based inheritance. JavaScript is a true object-oriented language. It draws inspiration from a number of other (relatively obscure) object-oriented languages that use prototype-based inheritance instead of class-based inheritance.

Although JavaScript is not a class-based object-oriented language, it does a good job of simulating the features of class-based languages such as Java and C++. I've been using the term class informally throughout this chapter. This section more formally explores the parallels between JavaScript and true class-based inheritance languages such as Java and C++.[*]

[*] You should read this section even if you are not familiar with those languages and that style of object-oriented programming.

Let's start by defining some basic terminology. An object, as you'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 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 dynamically added to an object. This is not the case in strictly typed languages such as Java and C++. In those languages, each object has a predefined set of properties,[*] where each property is of a predefined type. When you use JavaScript objects to simulate object-oriented programming techniques, you generally define in advance the set of properties for each object and the type of data that each property holds.

[*] They are usually called "fields" in Java and C++, but I'll refer to them as properties here since that is the JavaScript terminology.

In Java and C++, a class defines the structure of an object. The class specifies exactly what fields an object contains and what types of data each holds. It also defines the methods that operate on an object. JavaScript does not have a formal notion of a class, but, as shown earlier, it approximates classes with its constructors and their prototype objects.

In both JavaScript and class-based 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 the term instantiate is used to describe the process of creating an object (i.e., 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 lowercase letters. This convention helps keep classes and objects distinct from each other in code, and it is useful to follow in JavaScript programming as well. Previous sections of this chapter, for example, have defined a Rectangle class and created instances of that class with names such as rect.

The members of a Java class may be of four basic types: instance properties, instance methods, class properties, and class methods. In the following sections, we'll explore the differences between these types and show how they are simulated in JavaScript.

9.3.1. Instance Properties

Every object has its own separate copies of its instance properties. In other words, if there are 10 objects of a given class, there are 10 copies of each instance property. In our Rectangle class, for example, every Rectangle object has a property width that specifies the width of the rectangle. In this case, width is an instance property. Since each object has its own copy of the instance properties, these properties are accessed through individual objects. If r is an object that is an instance of the Rectangle class, for example, its width is referred to as:

 r.width 

By default, any object property in JavaScript is an instance property. To truly simulate traditional class-based object-oriented programming, however, we will say that instance properties in JavaScript are those properties that are created and initialized by the constructor function.

9.3.2. Instance Methods

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

 a = r.area( ); 

The implementation of an instance method uses the this keyword to refer to the object or instance on which it is invoked. 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 with instance properties. Instead, each instance method is shared by all instances of a class. In JavaScript, an instance method for a class is defined by setting a property in the constructor's prototype object to a function value. This way, all objects created by that constructor share an inherited reference to the function and can invoke it using the method-invocation syntax shown earlier.

9.3.2.1. Instance methods and this

If you are a Java or C++ programmer, you may have noticed an important difference between instance methods in those languages and instance methods in JavaScript. In Java and C++, the scope of instance methods includes the this object. The body of an area method in Java, for example might simply be:

 return width * height; 

In JavaScript, however, you've seen that you must explicitly specify the this keyword for these properties:

 return this.width * this.height; 

If you find it awkward to have to prefix each instance field with this, you can use the with statement (covered in Section 6.18.) in each of your methods. For example:

 Rectangle.prototype.area = function( ) {     with(this) {         return width*height;     } } 

9.3.3. Class Properties

A class property in Java is a property 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 property. Just as instance properties are accessed through an instance of a class, class properties are accessed through the class itself. Number.MAX_VALUE is an example of a class property in JavaScript: the MAX_VALUE property is accessed through the Number class. Because there is only one copy of each class property, class properties are essentially global. What is nice about them, however, is that they are associated with a class, and they have a logical nichea position in the JavaScript namespace where they are not likely to be overwritten by other properties with the same name. As is probably clear, you simulate a class property in JavaScript simply by defining a property of the constructor function itself. For example, to create a class property Rectangle.UNIT to store a special 1x1 rectangle, you can do the following:

 Rectangle.UNIT = new Rectangle(1,1); 

Rectangle is a constructor function, but because JavaScript functions are objects, you can create properties of a function just as you can create properties of any other object.

9.3.4. Class Methods

A class method is associated with a class rather than with an instance of a class. Class methods are invoked through the class itself, not through a particular instance of the class. The Date.parse( ) method (which you can look up in Part III) is a class method. You always invoke it through the Date constructor object rather than through a particular instance of the Date class.

Because class methods are invoked through a constructor function, the this keyword does not refer to any particular instance of the class. Instead, it refers to the constructor function itself. (Typically, a class method does not use this at all.)

Like class properties, class methods are global. Because they do not operate on a particular object, class methods are generally more easily 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 namespace and prevents namespace collisions. To define a class method in JavaScript, simply make the appropriate function a property of the constructor.

9.3.5. Example: A Circle Class

The code in Example 9-1 is a constructor function and prototype object for creating objects that represent circles. It contains examples of instance properties, instance methods, class properties, and class methods.

Example 9-1. A circle class

 // We begin with the constructor function Circle(radius) {     // r is an instance property, defined and initialized in the constructor.     this.r = radius; } // Circle.PI is a class propertyit is a property of the constructor function. Circle.PI = 3.14159; // Here is an instance method that computes a circle's area. Circle.prototype.area = function( ) { return Circle.PI * this.r * this.r; } // This class method takes two Circle objects and returns the // one that has the larger radius. Circle.max = function(a,b) {     if (a.r > b.r) return a;     else return b; } // Here is some code that uses each of these fields: var c = new Circle(1.0);      // Create an instance of the Circle class c.r = 2.2;                    // Set the r instance property var a = c.area( );             // Invoke the area( ) instance method var x = Math.exp(Circle.PI);  // Use the PI class property in our own computation var d = new Circle(1.2);      // Create another Circle instance var bigger = Circle.max(c,d); // Use the max( ) class method 

9.3.6. Example: Complex Numbers

Example 9-2 is another example, somewhat more formal than the last, that defines a class of objects in JavaScript. The code and the comments are worth careful study.

Example 9-2. A complex number class

 /*  * Complex.js:  * This file defines a Complex class to represent complex numbers.  * Recall that a complex number is the sum of a real number and an  * imaginary number and that the imaginary number i is the  * square root of -1.  */ /*  * The first step in defining   a class is defining the constructor  * function of the class. This constructor should initialize any  * instance properties of the object. These are the essential  * "state variables" that make each instance of the class different.  */ function Complex(real, imaginary) {     this.x = real;       // The real part of the number     this.y = imaginary;  // The imaginary part of the number } /*  * The second step in defining a class is defining its instance  * methods (and possibly other properties) in the prototype object  * of the constructor. Any properties defined in this object will  * be inherited by all instances of the class. Note that instance  * methods operate on the this keyword. For many methods,  * no other arguments are needed.  */ // Return the magnitude of a complex number. This is defined // as its distance from the origin (0,0) of the complex plane. Complex.prototype.magnitude = function( ) {     return Math.sqrt(this.x*this.x + this.y*this.y); }; // Return a complex number that is the negative of this one. Complex.prototype.negative = function( ) {     return new Complex(-this.x, -this.y); }; // Add a complex number to this one and return the sum in a new object. Complex.prototype.add = function(that) {     return new Complex(this.x + that.x, this.y + that.y); } // Multiply this complex number by another and return the product as a // new Complex object. Complex.prototype.multiply = function(that) {     return new Complex(this.x * that.x - this.y * that.y,                        this.x * that.y + this.y * that.x); } // Convert a Complex object to a string in a useful way. // This is invoked when a Complex object is used as a string. Complex.prototype.toString = function( ) {     return "{" + this.x + "," + this.y + "}"; }; // Test whether this Complex object has the same value as another. Complex.prototype.equals = function(that) {     return this.x == that.x && this.y == that.y; } // Return the real portion of a complex number. This function // is invoked when a Complex object is treated as a primitive value. Complex.prototype.valueOf = function( ) { return this.x; } /*  * The third step in defining a class is to define class methods,  * constants, and any needed class properties as properties of the  * constructor function itself (instead of as properties of the  * prototype object of the constructor). Note that class methods  * do not use the this keyword: they operate only on their arguments.  */ // Add two complex numbers and return the result. // Contrast this with the instance method add( ) Complex.sum = function (a, b) {     return new Complex(a.x + b.x, a.y + b.y); }; // Multiply two complex numbers and return the product. // Contrast with the instance method multiply( ) Complex.product = function(a, b) {     return new Complex(a.x * b.x - a.y * b.y,                        a.x * b.y + a.y * b.x); }; // Here are some useful predefined complex numbers. // They are defined as class properties, and their names are in uppercase // to indicate that they are intended to be constants (although it is not // possible to make JavaScript properties read-only). Complex.ZERO = new Complex(0,0); Complex.ONE = new Complex(1,0); Complex.I = new Complex(0,1); 

9.3.7. Private Members

A common feature of traditional object-oriented languages such as Java and C++ is that the properties of a class can be declared private so that they are available only to the methods of the class and cannot be manipulated by code outside of the class. A common programming technique called data encapsulation makes properties private and allows read and write access to them only through special accessor methods. JavaScript can simulate this using closures (an advanced topic, covered in Section 8.8.), but to do so, the accessor methods must be stored on each object instance; they cannot be inherited from the prototype object.

The following code illustrates how this is done. It implements an immutable Rectangle object whose width and height can never be changed and are available only through accessor methods:

 function ImmutableRectangle(w, h) {     // This constructor does not store the width and height properties     // in the object it initializes.  Instead, it simply defines     // accessor methods in the object.  These methods are closures and     // the width and height values are captured in their scope chains.     this.getWidth = function( ) { return w; }     this.getHeight = function( ) { return h; } } // Note that the class can have regular methods in the prototype object. ImmutableRectangle.prototype.area = function( ) {     return this.getWidth( ) * this.getHeight( ); }; 

Douglas Crockford is generally credited as the first person to discover (or at least to publish) this technique for defining private properties. His original discussion is at http://www.crockford.com/javascript/private.html.




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net