Prototypes are simply mechanisms by which Javascript objects inherit properties from other JavaScript objects.
We already know how to create objects using object notation, and how to add or remove properties from such objects.
let object = { val1: "Hi", val2: true } object.val1 = "Hello!"; object.val2 = false; object.valArray = [1, 2, true];
But what if we would need to create hundreds of different variations of object
? Writing all this syntax would be tedious and would clutter and reduce our readability. So what is the workaround?
The new operator
JavaScript isn’t an object-oriented language and thus doesn’t have the encapsulation and inheritance capabilities that real OO languages have. Inheritance and encapsulation are helpful concepts that help us extend features of one object into another, or hide objects and certain properties. So we might naturally wonder, how can we implement a similar mechanism, in order to create multiple instances that have the same blueprint to reduce bloat, preserve code length and keep readability. Thankfully JavaScript provides the new
operator that is applied to a constructor function, and which will create a newly allocated object by instantiating it using a constructor.
function Car() { } let car = Car(); console.log(car); let carInstance = new Car(); console.log(carInstance);
If we invoke the car function as a regular function expression, using let car = Car();
nothing happens when we try to print out its contents, but when we use the constructor variation using new
, the function gets a new object assigned to its prototype object. JavaScript has created a new object and assigned it an internal property that is not directly accessible ([[Prototype]]
in the screenshot below). We also opened the constructor object and see properties associated with the constructor, such as the arguments
, caller
, length
and name
of the constructor.
We can extend that object as any other object by adding a random function to it:
Car.prototype.getSpeed = function() { console.log("Speed"); } console.log(carInstance);
Now if we take a look at our prototype object we will see that the context of the function is changed.
So what happened here? Every function created gets assigned a new prototype object. When we use a function as a constructor, the constructed object’s prototype is set as the function’s prototype. We extended the Car.prototype
with the getSpeed
function and when we try to access the getSpeed()
property we would first check inside the function itself, and then the search will be delegated to the constructor of that function. This actually means that the getSpeed
function is a property of the prototype and not of the instances.
Prototypes and instance properties
So, we create a function as a constructor using new
and JS creates a new object instance which it assigns to the function we’ve just created. We can then use the exposed parameters, but in addition, can initialize new parameters using the this
keyword.
function Car() { this.doors = 4; this.maxSpeed = 240; this.getSpeed = function() { return this.maxSpeed; } } Car.prototype.getSpeed = function() { return 120; } let carInstance = new Car(); console.log(carInstance); console.log(carInstance.getSpeed());
The variables and methods above created with the help of this
keyword are called instance properties. Why are they helpful? Because the instance properties will override the properties found in the prototypes. JS looks first in the local instance before moving out to the prototype, and thus the value we would get if we call getSpeed()
is actually 240, because that is the value that is found constructor of the Car()
method.
There is no need to traverse the prototype chain, if we already created a property inside the constructor function, directly on the new carInstance
instance. We would always try and pick local instances if they exist. Only if we don’t find any references inside the local instance we would look it up in the prototype chain. How can we utilize this behavior?
Often it is good enough to create methods on local instances. There is a caveat, though. Each instance gets its own version of the properties, and they share the same properties among themselves. This could become problematic when a large number of objects are created. Copying the same method among different instances consumes more memory. In cases such as these, we might decide to place objects methods on the function’s prototype, and then we can have a single method shared by all instances of an object.
Final Words
Knowing the ins-and-outs of prototypal inheritance is useful. If we use the prototype we can find the associated properties. We could also check the instance to know if it belongs to a certain constructor.
let carInstance = new Car(); console.log(carInstance instanceof Car)
The instanceof
operator gives us a way to determine the instance and whether it was created by a certain constructor.
For more articles please click below, or check the blog.
- Largest Palindrome Product
- Largest Prime Factor
- Even Fibonacci Numbers
- Multiples of 3 or 5
- How to find the missing number in a given integer array of 1 to 100?
- Understanding Javascript prototypes
- Difference between Promise.all() and Promise.race()
- JavaScript generators
- Using this and arguments
- Arrow functions in JavaScript