JavaScript 101: Advanced Objects using Prototypes

So, this brings us to the last chapter of HFJS! I am very impressed with the book and it's comical, casual approach to teaching which makes even the hardest-to-get topics a cakewalk. We saw two methods of creating objects till now:
1. Literals
2. Constructors

Now let's study the third, and the most powerful way of creating objects: Prototypes.
So the chapter starts off with a warning that this type of object system is quite different from the normal class based OO found in other languages. In this, classes don't exist at all (though now they
do, in ES6). Instead, we inherit and extend objects from other objects. What that means, I'm gonna find out.

The disadvantage of Constructors
In simple words, constructors enable code reuse at the code level. But at run time, the situation is same. The methods written into an object constructor are replicated for each object. So if I define a Car constructor with a function honk(), which is same for all cars, and create say 100 cars, the exact same function will be replicated into each object. This does not scale well and adds to the memory overhead of the browser. It is even more harmful on mobile browser, due to their limited resources.

Working with Prototypes
Prototypes are the objects which we inherit from, to create new objects. A prototype contains all the methods and properties that are common to ALL the instances. Not only the property, the value of the
property must be common for ALL instances, for it to be included in a prototype. For example, the Car prototype will contain honk() method, and wheels property with value 4, because it is same for ALL cars. It will not contain "year" or "model", because there will of course be differing values for different objects, for these properties.

How inheritance works
If we have a Dog object tito with a method bark() method in it's prototype, and we call tito.bark(), following events will happen:
1. The tito object will be searched for a bark() method
2. No such method exists in tito object
3. Therefore it's prototype will be referred to. It does contain the bark() method, therefore it will be called accordingly

This is similar for properties, that are stored in the prototype.

Overriding Prototypes
Suppose we have a Dog object bruno, and we don't want it to bark in a similar fashion like all the other dogs. We can easily do that by writing a custom bark() method in the object itself. Now, when the bark() method is called on bruno, as seen previously, the object will be searched for bark(), and it will be found. Then and there, it will be called and the prototype will not be even referred. However, all the other dogs can continue using the prototype's bark() method.

Prototypes in Action
Let us see a live example of prototypes now. We will be using a Dog class. Now, where to access the prototype? Constructor! Yes, all constructors have a prototype property which stores all the methods
and properties that are supposed in the prototype. So, first we create the constructor, and put all the properties whose value will be object specific, in there:

function Dog (name, age) {
        this.name = name;
        this.age = age;
}

Next, we access the prototype, to add all the common valued properties and common methods there:

Dog.prototype.legs = 4;
Dog.prototype.breed = "Labrador";
Dog.prototype.bark = function() {
        console.log("Woof");
};

Thus, now we have a complete Dog prototype and constructor with all the methods and properties. Now we will create an object:

var tito = new Dog("tito", 3);

and call it's bark method:

tito.bark()

this will result in the bark() in prototype getting executed. If we wanted to create bruno object with a different bark, here is how:

var bruno = new Dog("bruno", 5);
bruno.bark = function() {
        console.log("Wufffff");
};

That's it. Now, while searching for bark() in the object, it will be found, and no need to look for in the prototype. Now, if we wanted to add a new method to all dogs, we can do so simply by adding a method to prototype:

Dog.prototype.sit = function() {
        console.log(this.name + " sat down.");
};

This sit() can be called on any Dog object now, created in the past or future. Similarly, we can change a method or property in the prototype, and all the objects will be affected, unless the override the prototype.

Important: The this pointer in the prototype methods continues to point to the object on which the method is called, not the protoype. That's how we were able to access the dog's name in the sit() function above. When you call an object’s method, this is set to the object whose method was called. If the method is not found in that object, and is found in the prototype, that doesn’t change the value of this.

Changing the value of an inherited property
What happens when we change the value of a property, which has been inherited from the prototype, of an object? Let's try out:

First, we create a Dog using constructor:

var leo = new Dog("leo", 4);

Now, as per prototype, Leo is of labrador breed. However, we want to change that. So we do: leo.breed = "pomeranian". Now you may ask, but breed property is in prototype. So, now all dogs have pomeranian breed? The answer is no. Let us see step wise:

1. We create leo object, which has breed = "labrador", as per prototype. It DOES NOT have a breed property of it's own.
2. We set leo.breed = "pomeranian"
3. Now, a new property of name breed is created and stored in Leo's object. It is different from the prototype, and contains a different breed, pomeranian.
4. Now whenever we try to access breed property of leo, it's the one in the object that we are accessing, not the one in the prototype.
5. The breed property in the prototype remains untouched.

No comments:

Post a Comment