In this article we will discuss the following points:
a. Constructor Functions
b. What are prototypes?
c. How to implement inheritance.
I. Constructor Functions:
A function constructor can be simply defined like an ordinary function, and usually the function name is capitalized(a convention).
There are two major differences between a constructor function and an plain old function:
a. Constructor functions are able to declare “members”, i.e. properties and methods.
b. Constructor functions are not required to return any value, thus if a function returns a value other than “this”, it’s not a function constructor.
For example, here’s how we can create a constructor function that can be used to instantiate computers:
To utilize this “constructor function”, we use the keyword “new”:
If we open the developer’s tools of firefox we can see the results:
The keyword “new” actually instantiates an object using the constructor function,
and this function automatically returns an instance created by this function and assigns the reference to the variable “officeComputer”, which is why we do not and cannot explicitly return a value, since the value implicitly returned from a constructor function is an object instance.
When we invoke the method “display” of this instance, we can see the text displayed in the console, where the properties are concatenated with the string literals.
In addition, we can alter the value of the properties of this instance, e.g. setting the CPU to ‘AMD’, and invoke the method “display” again to see a change in the text displayed.
Therefore, the properties and methods are relatively “public”.
(p.s. the “privateVariable” is only visible to members within the constructor function. Check my “Introduction to Closures” if you don’t understand what I mean.)
II. What are prototypes?
Before we dive into this topic, we want to know why we need prototypes.
Suppose we created 1 million instances of computers, this actually means that for each instance, two function references are held, and that sums up to 2 million references in total, which takes up a huge load of memory.
But since the methods for each instance does exactly the same thing, why do we need so many references then? This is when prototypes come in handy.
According to wikipedia, the definition for prototypes is:
“Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects via delegation that serve as prototypes.”
(For reusability, we will reuse the class Computer defined earlier in our examples.)
To harness the benefits of prototypes, we can avoid re-defining common methods by revising the function constructor.
There are two things that you need to take note of.
First of all, we use the keyword “prototype” preceded by the name of the constructor function as well as the “.” operator to define a method.
Since this method is defined in the prototype of the class “Computer”, every single instance created using the keyword “new” will reference to the same method.
The second thing is that the function defined in the prototype still has access to the member properties using the keyword “this”. Everything else will remain the same as if nothing happened to the definition of the constructor function.
III. How to implement inheritance
Suppose, for example, what if our client’s request is to build “super computers“ that can perform complex calculations such as weather prediction?
If we define another constructor function, then we are definitely wasting too much time declaring duplicate members.
Here’s how we can use prototypes to simulate inheritance.
First we define the subclass:
Notice that in this constructor function, we can invoke the constructor function of “Computer“ by passing “this“ to the function “call“ , this enables objects created by “SuperComputer“ gain access to properties defined in “Computer“.
(Reference: MDN Function.prototype.call)
Then we set the prototype of this subclass to an instance of “Computer”:
We are setting the prototype of SuperComputer to an instance of Computer.
Remember that all instances created by “Computer” will gain access to members defined in Computer’s prototype, but since now we are trying to extend the Computer and design a SuperComputer, we need to access the prototype through the instance of a “Computer”, in fact we are actually creating a prototype chain like the picture below:
Thus, for every method invocation or property access, the object itself is first looked up, if not found then its prototype is tried, and then the next prototype on the prototype chain, and so on, until no more prototypes exist, i.e. null.
(p.s. The last stop is Object.prototype)
Finally we can define some methods for the subclass:
And a test run:
The results are shown below:
To conclude, prototypes are very useful no matter if you are required to create tons of objects, or if you only need a one-time usage object, e.g. anonymous objects.
All objects will have access to the methods or properties defined in the prototype. This is why built-in objects such as Array, String already have numerous utility methods defined in the prototype. To see how useful they are, check String Array for more information.