Chapter 9. Prototypes and Inheritance (The Function Object)

The latter half of Lesson 8 taught you how to create your own data types by creating a constructor function in conjunction with the this variable to create properties and methods. We'll pick up with the Point data type; its code is repeated here for your convenience:

function Point(x, y) {
    this.x = x;
    this.y = y;
    this.getDistance = function(point) {
        var x = Math.pow(point.x - this.x, 2);
        var y = Math.pow(point.y - this.y, 2);

        return Math.sqrt(x + y);
    };
}

This implementation has a slight flaw. When calling the constructor with the new keyword, JavaScript creates a Point object, and in doing so it has to recreate everything within the constructor every time it's called. That is, the x and y properties, and a new Function object to serve as the getDistance() method. In JavaScript, functions are objects, too — complete with their own properties and methods.

In an object-oriented environment, creating a new object should also create its properties. Properties, and more importantly their values, are specific to each instance of a data type, and they should rarely be shared with all other instances of a particular data type. For example, a Point object's x and y properties contain values that are independent from another Point object's x and y properties. Changing one Point's x property should not affect another Point object's x property. So a Point's x and y properties should be created when the Point object is created.

A Point object's methods, however, are another matter. From a developer's standpoint, you want a Point object to behave like every other Point object in an application. A change made to a Point object's behavior should be reflected in all other Point objects; otherwise you could not depend on any object to behave how you need it to behave.

In JavaScript, an instance of the Function data type is created every time a function is defined or declared. That means that with every call to the Point() constructor, JavaScript creates a new function instance for the getDistance() method. JavaScript shouldn't have to create new behavior with every new object that is created; it should use an already existing behavior and share that behavior with all objects of the same type. This is achieved by means of the prototype property.

USING THE PROTOTYPE OBJECT

Every Function object has a prototype property. It is an object that contains properties and methods that are shared among all instances of a particular data type. That means that a change made to any property or method of a particular data type's prototype object is seen by all instances of that data type. The prototype object is very much a prototype for every object created with a constructor; the properties and methods defined on a data type's prototype become members of an object.

That being said, properties generally should be created only in the constructor, not the prototype object, because properties are typically pieces of data that are unique to each instance of a data type. On the other hand, the shared nature of the prototype object makes it an ideal place to create methods.

To access a Function object's prototype object, use the function's name, followed by a dot and the word prototype. The following code rewrites the Point data type to include the use of the prototype property:

function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.getDistance = function(point) {
    var x = Math.pow(point.x - this.x, 2);
    var y = Math.pow(point.y - this.y, 2);

    return Math.sqrt(x + y);
};

Here, the getDistance() method is moved out of the constructor and added to the Point.prototype object. This is advantageous for two reasons:

  • Only one Function object is created and assigned to getDistance. Calling the Point() constructor now does not create a new Function object for getDistance().

  • The getDistance() method is shared among all Point objects. Changes made to the method are seen by all Point objects.

Even though this method is now created on the prototype, you still access the method from an object in the same way, as shown in this code:

var point1 = new Point(0, 0);
var point2 = new Point(1, 1);

point1.getDistance(point2);

This works because of the way JavaScript looks up identifiers on objects. When accessing this getDistance() method, JavaScript first performs a search for the getDistance identifier on the object itself — looking for an identifier with the same name. If JavaScript finds a match, it returns the match's value (remember: Functions can be values). However, if it does not find a match, JavaScript searches for the getDistance identifier in the data type's prototype object and returns either the value if a match is found or undefined if one could not be found.

Because of this behavior, it's possible to override methods defined in the prototype object. To do this, simply assign a value to the getDistance identifier, like this:

var point1 = new Point(0, 0);
var point2 = new Point(1, 1);

point1.getDistance = function(point) {
    alert("This method no longer works.");
};

point1.getDistance(point2); // message in alert window

Look at the bold portion of this code. Here, a new function object is created and assigned to the getDistance member on the object. You might think that this code alters the getDistance() method on the prototype, but it doesn't. The Point.prototype.getDistance() method can only be modified by directly accessing it through the prototype object (that is Point.prototype.getDistance = new value). This code actually creates a new getDistance() method on the point1 object itself, essentially overriding the getDistance() method defined on the prototype.

So now when the getDistance() method on point1 is accessed, JavaScript searches for the getDistance identifier, finds a match on the point1 object, and returns the newer function. The original getDistance() method on the prototype object is still intact, and the following code demonstrates this:

point2.getDistance(point1); // returns 2

Here, the code calls the point2.getDistance() method. The point2 object does not actually have a getDistance member, so JavaScript pulls the value from Point.prototype.getDistance — the function that performs the distance calculation.

Be wary of overriding methods in this manner. The whole idea behind using the prototype object is that members, usually methods, don't have to be created for each object. Besides, overriding a method like this can cause undesired results.

Note

It is worth reiterating that properties typically don't belong on the prototype. Properties generally should be created inside the constructor.

INHERITING MEMBERS FROM OTHER OBJECTS

All objects in JavaScript inherit from the Object data type. Inheritance is an object-oriented concept not too dissimilar from the idea or definition probably running through your head. It's not the type of inheritance by which one gets an heirloom from a family member, or a specific genetic trait, like eye or hair color. It's the type of inheritance by which a sedan has the properties and behaviors of an automobile.

In object-oriented programming, inheritance is all about reusing code. You have a base data type (called the base type, super type, or parent type), and you extend that data type by creating a new data type (called a sub-type or child type) that inherits properties and methods from the base type while also providing new functionality specific to the child-type.

Let's say you've been commissioned to write a portion of an application that keeps track of all employees within a given company. The portion you are in charge of is to write a data type that represents an individual employee. The application needs to know the person's name and title. You incorporated a modified version of your Person data type created in Lesson 8, and another developer on your team is already using it for the portion of the app he is responsible for. The modified Person type looks like this:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

Person.prototype.getFullName = function() {
    return this.firstName + " " + this.lastName;
};

It's a simple data type. It has properties for the person's first and last name, as well as a method to concatenate the two into the person's full name. A lot of work is already done with this data type. Wouldn't it be great if you could use it without altering it (otherwise, your coworker's code may break), while still adding the functionality you need? Inheritance allows you to do so.

Note

There are a number of inheritance patterns in JavaScript, each with its pros and cons. This book teaches you just one pattern.

The first step is to create a new data type that will inherit from the Person data type. This new type is called Employee, and the constructor function has three parameters: the employee's first and last name, and his or her position.

function Employee(firstName, lastName, position) {
    this.position = position;
}

This constructor function creates a position property and assigns it the value passed to the argument of the same name, but nothing is done with the firstName and lastName parameters. As it stands right now, Employee objects have only one property. So Employee needs some way of gaining the firstName and lastName properties of the Person type. This is where the call() method of Function objects comes into play.

The call() method allows you to call a function as if it was a method of a specific object. In other words, it calls a method with a specified value for the this variable. The idea is probably easier to understand with some code. The following bolded code adds a new line to the Employee() constructor function:

function Employee(firstName, lastName, position) {
    Person.call(this, firstName, lastName);
    this.position = position;
}

So what happens here is that the value of this (the Employee object being created) is passed to the call() method, which gives the this variable in Person() the value of the Employee object being created. So, essentially, Person() executes as if it were Employee().

The other arguments passed to the call() method reflect the parameters of the Person() function: firstName and lastName. The result of this code is that firstName and lastName properties are added to Employee objects when they are created. So, Employee has now inherited Person's properties, but it still needs to inherit the getFullName() method.

If getFullName() were defined inside the constructor, inheritance would be complete and you'd be done. But the method is defined in Person's prototype, so you'll use something called prototype chaining to inherit Person's prototype. The process of prototype chaining is a simple one: Simply create a new instance of the base-type and assign it to the sub-type's prototype, as in this bolded code:

function Employee(firstName, lastName, position) {
    Person.call(this, firstName, lastName);
    this.position = position;
}

Employee.prototype = new Person();

Prototype chaining gets its name because it adds another prototype to the search list, much as one would add a new link to a chain. Now when you create an Employee object and call the getFullName() method, JavaScript first looks at the object for a match. It does not find a match, so it looks at Employee's prototype and doesn't find a match there either. So it looks at Person's prototype, since that is a new "link" in the "chain" of prototypes, finds a match, and executes it. Of course, the more prototypes you add to the chain, the more JavaScript has to search for items in prototypes. So it's a good idea to keep prototype chains relatively small.

You can now create an Employee object and use the firstName, lastName, and position properties, as well as call the getFullName() method. The following code demonstrates this:

var employee = new Employee("Jeremy", "McPeak", "Author");

alert(employee.firstName); // Jeremy
alert(employee.lastName); // McPeak
alert(employee.getFullName()); // Jeremy McPeak

There's still more to be done, however, so now it's time to...

TRY IT

In this lesson, you learn how to use prototypes to make your code more efficient by creating fewer objects. You also learn about the Function's call() method, and how to chain prototypes to make inheriting a data type's prototype possible. In this section you learn how to override a base-type's method in a sub-type.

Lesson Requirements

For this lesson, you need a text editor; any plain text editor will do. For Microsoft Windows users, Notepad is available by default on your system or you can download Microsoft's free Visual Web Developer Express (www.microsoft.com/express/web/) or Web Matrix (www.asp.net/webmatrix/). Mac OS X users can use TextMate, which comes as part of OS X, or download a trial for Coda (www.panic.com/coda/). Linux users can use the built-in VIM.

You also need a modern web browser. Choose any of the following:

  • Internet Explorer 8+

  • Google Chrome

  • Firefox 3.5+

  • Apple Safari 4+

  • Opera 10+

Create a subfolder called Lesson09 in the JS24Hour folder you created in Lesson 1. Store the files you create in this lesson in the Lesson09 folder.

Step-by-Step

The getFullName() method needs to include the employee's position. Follow these steps to override Person's getFullName() method to provide that functionality.

  1. Open your text editor and type the following function:

    function Person(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    Person.prototype.getFullName = function() {
        return this.firstName + " " + this.lastName;
    };
    
    function Employee(firstName, lastName, position) {
        Person.call(this, firstName, lastName);
        this.position = position;
    }
    
    Employee.prototype = new Person();

    This is the Employee data type from this lesson.

  2. Now add a method called getFullName() to Employee's prototype object. It is bold in the following code:

    // Person omitted for printing
    
    function Employee(firstName, lastName, position) {
        Person.call(this, firstName, lastName);
        this.position = position;
    }
    
    Employee.prototype = new Person();
    Employee.prototype.getFullName = function() {
    };
  3. The Person data type's getFullName() method has functionality that we want to use. To save time recoding that portion of the method, use the call() method to call Person's getFullName() method and assign the result to a variable. The new code is bold in the following:

    // Person omitted for printing
    
    function Employee(firstName, lastName, position) {
        Person.call(this, firstName, lastName);
        this.position = position;
    }
    
    Employee.prototype = new Person();
    Employee.prototype.getFullName = function() {
        var fullName = Person.prototype.getFullName.call(this);
    };

    This code calls the Person.prototype.getFullName() method in the context of the current object (the Employee object). The call() method has to be used here because getFullName() for Employee objects now has a new value (the method you're creating right now).

  4. Append the employee's position to the string in fullName and return it:

    function Employee(firstName, lastName, position) {
        Person.call(this, firstName, lastName);
        this.position = position;
    }
    
    Employee.prototype = new Person();
    Employee.prototype.getFullName = function() {
        var fullName = Person.prototype.getFullName.call(this);
    
        return fullName + ", " + this.position;
    };
  5. Once again, add the bold code:

    function Employee(firstName, lastName, position) {
        Person.call(this, firstName, lastName);
        this.position = position;
    }
    
    Employee.prototype = new Person();
    Employee.prototype.getFullName = function() {
        var fullName = Person.prototype.getFullName.call(this);
    
        return fullName + ", " + this.position;
    };
    
    var employee = new Employee("Jeremy", "McPeak", "Author");
    
    alert(employee.getFullName());
  6. Save the file as lesson09_sample01.js.

  7. Open another instance of your text editor, type the following HTML, and save it as lesson09_sample01.htm.

    <html>
    <head>
        <title>Lesson 9: Prototypes and Inheritance</title>
    </head>
    <body>
        <script type="text/javascript" src="lesson09_sample01.js"></script>
    </body>
    </html>
  8. Open the HTML file in your browser. An alert window displays the result of calling employee.getFullName(): a string containing the values passed to the Employee() constructor.

To get the sample code files you can download Lesson 9 from the book's website at www.wrox.com.

Note

Please select Lesson 9 on the DVD to view the video that accompanies this lesson.

Step-by-Step
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.176.72