Topic 9 - JavaScript Prototypes and Classes

Important!

This topic is only intended for students who are up-to-date. You must complete Topics 1-4, and 6-8,before you attempt this.

In this topic we will look at JavaScript prototypes.

Constructors and Prototypes

In the first session this week we looked at JavaScript objects. However, there was a problem in which we were unable to easily set up many versions of similar objects, such as many cats which all have different names but all work in the same way, i.e. all have the same methods. From what we have seen already, the only way of doing this is to add the methods (such as eat(), walk() and makeNoise()) to every single object of a given type (e.g. every single cat). Clearly, this is inefficient and time-consuming, as the following code illustrates:

var cat = {
        name: "Tiddles",
        age: 10,
        weight: 10,
    
        makeNoise: function()
        {
            alert("Meow!");
        },

        walk: function()
        {
            // "--" reduces the weight by 1
            this.weight--;
        },

        eat: function()
        {
            // "++" increases the weight by 1
            this.weight++;
        }

    };

var cat2 = {
        name: "Tom",
        age: 5,
        weight: 5,
    
        makeNoise: function()
        {
            alert("Meow!");
        },

        walk: function()
        {
            // "--" reduces the weight by 1
            this.weight--;
        },

        eat: function()
        {
            // "++" increases the weight by 1
            this.weight++;
        }

    };
    
    
To create two cats with different properties, we have to repeat the code for the methods which is inefficient as the methods work in the same way for each cat. This topic will examine how we can get round this problem.

The Constructor

If you have had experience of other object-orientated languages, you will have come across the constructor. The constructor is a special method used to define how objects are created. In JavaScript, the constructor works in a different way to other languages. It is an ordinary function which is used to create objects when used together with the "new" keyword. What do we mean by this? Consider this function which could be used as a possible constructor:

function Cat(n, a)
{
    this.name = n;
    this.age = a;
}
When used with new, this constructor function can be used to create new Cats. It takes two parameters, n (the name) and a (the age), and sets the name and age properties of objects created using this constructor to those two parameters. ("this" refers to the object that will be created with the constructor). So we could use the constructor function to create two new Cat objects:

var cat1 = new Cat("Tiddles", 10);
var cat2 = new Cat("Tigger", 7);

// Display the cats to show that it worked
alert(cat1.name + " " + cat1.age);
alert(cat2.name + " " + cat2.age);
When the new keyword is used in code together with a function name (e.g. Cat) this tells JavaScript to create a new object using the specified function (Cat here). Note that unlike other languages, Cat is not a data type. It is simply a function name.

In fact, one could call Cat just like a regular function, but it wouldn't do a lot in that case. For instance the code

var cat1 = Cat("Tiddles",10); // note no "new"
would be perfectly legal JavaScript, but wouldn't do anything, unless the Cat function returned something, in which case the variable cat1 would be assigned the return value of the Cat function.

Prototypes

The problem we have so far is that we have to repeat code for several objects of a similar type. This also has the effect of creating multiple copies of methods in memory for each object of that type, which is very memory-inefficient. We can get round this problem using prototypes.

Constructor functions have a special property, prototype, which defines a "blueprint" or "template" object to use when creating new objects with that constructor. (How can a function, such as the constructor, have properties? In JavaScript, functions are a form of object, so because a function is an object, it can have properties). So we can define a prototype for cats, by assigning properties and methods to the prototype property of the Cat constructor, and then create many Cats which use that prototype. Then, only one copy of these shared properties and methods need be stored in memory..

The code below shows this:

function Cat(n,a,w)
{
    this.name=n;
    this.age=a;
    this.weight=w;
}


Cat.prototype.species = "Felis catus";
Cat.prototype.nLegs = 4;
Cat.prototype.makeNoise = function() { alert("Meow!"); };
Cat.prototype.eat = function() { this.weight++; };
Note how we use the prototype property of the constructor function, Cat. The prototype property represents the prototype of all objects created with this constructor function, in other words the prototype property of all cats. So, all cats will have a prototype containing: It should be noted that the prototype is not part of the individual cat objects themselves. It is a separate object which is shared between all objects created with the Cat constructor, i.e. all cat objects. The diagram below illustrates this.

Object prototypes

We can prove that the prototype works with the following code:

function Cat(n,a,w)
{
    this.name=n;
    this.age=a;
    this.weight=w;
}

// Setup the prototype in the "global" area, outside any functions
Cat.prototype.species = "Felis catus";
Cat.prototype.nLegs = 4;
Cat.prototype.makeNoise = function() { alert("Meow!"); };
Cat.prototype.eat = function() { this.weight++; };

// Function to test creating objects with prototypes - might be linked to a button
function testprototypes()
{
    var cat1 = new Cat("Tiddles", 10, 10);
    var cat2 = new Cat("Tigger", 7, 8);

    alert(cat1.species); // "Felis catus"
    alert(cat2.species); // "Felis catus"
    cat1.makeNoise(); // "Meow!"
    cat2.makeNoise(); // "Meow!"
}

Note how, even though makeNoise() and species are part of the prototype, we can access them directly using simply the object name plus the method or property, for example cat1.makeNoise() (we don't have to use something like cat1.getPrototype().makeNoise()). The reason for this is given under the discussion on "prototype chaining", in the further notes on prototypes.

Exercise 1

Return to your car exercise from the objects topic.

  1. Create a Car constructor which takes parameters for the make, model, engine capacity, and top speed, and sets up corresponding properties. It should also set a currentSpeed property to 0.
  2. Set up a prototype object belonging to the Car constructor and containing all the car methods you wrote last time, in other words, toString(), accelerate(), decelerate(), and so on.
  3. Test it out by creating two car objects which use this prototype, and doing things with them, such as accelerating, decelerating and displaying them.

Classes

Above we looked at prototypes as a way of creating several objects of a similar type, for example several cats. The only issue with prototypes is that the code can be rather messy and hard to understand, and common object-oriented concepts such as inheritance are hard to implement. Thus, ECMAScript 6 introduced classes to JavaScript, which work in a similar way to classes in full object-oriented languages such as Java.

Note that it is important to understand that JavaScript doesn't use real classes, in the way that other languages such as Java do. JavaScript classes are "syntactic sugar" (as stated in the Mozilla article, below), round JavaScript's real underlying object mechanism, prototypes, which we looked at above.

Classes and Objects

So far we've looked at objects, but object-oriented programming also features the concept of classes. As of ECMAScript 6, you can use classes in JavaScript. What is a class? It is a general description, or blueprint, of something while an object is a particular example, or instance, of that class. For example:

More detail can be found on the Mozilla page.

Here is a simple example where we define a class representing a student.



class Student
{

    
    // constructor
    constructor(nameIn,courseIn)
    {
        this.name = nameIn;
        this.course = courseIn;
    }

    display()
    {
        alert(`Name ${this.name} Course ${this.course}`);"
    }
}


function init() 
{
    var student1 = new Student("John" , "Software Engineering");
    var student2 = new Student("Surjan" , "Web Design");
    student1.display();
    student2.display();
}

How does the example work?

Another example: a Dog class


class Dog
{
    constructor(nameIn, weightIn)
    {
        this.name = nameIn;
        this.weight = weightIn;
    }

    display()
    {
        alert(`${this.name} ${this.weight}`);
    }

    walk()
    {
        if(this.weight > 10)
        {
           this.weight--; // reduce weight by 1
        }
        else
        {                
            alert("The poor dog will be half starved if you carry on!");
        }
    }
}

function init()
{
    var dog1 = new Dog("Fido", 11);
    var dog2 = new Dog("Lassie", 13);
    dog1.display();
    dog2.display();
    dog1.walk();
    dog1.display();
    dog1.walk();
    dog1.display();
}

?>

Note how the walk() method of Dog is defined in the class. It simulates walking the dog by reducing the weight by one, but if the weight goes below 10, the dog refuses to walk as he/she doesn't want to starve!!!

Inheritance

// Vehicle.js
class Vehicle 
{

    constructor(makeIn, topSpeedIn, nWheelsIn)
    { 
        this.make = makeIn;
        this.topSpeed = topSpeedIn;
        this.nWheels = nWheelsIn;
    }

    move()
    {
       alert("Moving along...");
    }

    display()
    {
        alert(`${this.make} ${this.topSpeed} ${this.nWheels}`);
    }
}
// Bike.js

class Bike extends Vehicle 
{
    constructor(makeIn, topSpeedIn, offRoadIn, nGearsIn)
    {

        //Call the superclass (Vehicle) constructor to construct the Vehicle
        // component of the Bike. We will pass in the top speed, make, and
        // number of wheels (which we know will be 2)
        super(makeIn,topSpeedIn,2);

        // Set up the Bike-specific properties
        this.isOffRoad = offRoadIn;
        this.nGears = nGearsIn;
    }

    display()
    {
        super.display();
        alert(`Off road? ${this.isOffRoad} No. Gears: ${this.nGears}`);
    }
}

Exercise 2

Rewrite your code from Exercise 1 to use classes, rather than prototypes.

Online References - Prototypes

The presentation of the material on prototypes was inspired by the following online references which you might find useful:

As always I would recommend David Flanagan's "JavaScript - The Definitive Guide" for further reading.