Understanding JavaScript Prototypes
Prototypes allow JavaScript objects to share properties and behaviors. What is a prototype, how do prototype chains function, and how do I set an object’s prototype? These are all covered in this article.
Prototypes
People who are just beginning to learn JavaScript, especially those coming from a C++ or Java background, sometimes find the language’s prototype to be baffling.
Inheritance in JavaScript is handled somewhat differently than in C++ or Java. The term “prototypical inheritance” better describes the style of inheritance used in JavaScript.
When you throw in JavaScript’s class concept, things get much more confusing. The new class syntax is visually reminiscent of C++ and Java, but it is fundamentally different in operation.
This article will attempt to explain the concept of “prototypal inheritance” as it applies to JavaScript. We also investigate the new class-based syntax in an effort to fathom its inner workings. Let’s go in right now.
For the purpose of explanation, consider the following case:
function Point2D(x, y) {
this.x = x;
this.y = y;
}
When the Point2D function is declared (recall that in JavaScript, a function is actually an object), a prototype property will be automatically created for it. Point2D.prototype.constructor = Point2D defines the prototype property as an object with a constructor property whose value is the Point2D function. In addition, any objects produced by invoking Point2D with the new keyword will take on the characteristics of the Point2D.prototype instance. You may ensure this by including the following code in your copy of Point2D.prototype:
Point2D.prototype.move = function(dx, dy) {
this.x += dx;
this.y += dy;
}
var p1 = new Point2D(1, 2);
p1.move(3, 4);
console.log(p1.x); // 4
console.log(p1.y); // 6
All objects made with the new Point2D(…) syntax should be modeled after the Point2D.prototype object. The Point2D.prototype object can have any number of additional properties added to it. Point2D.prototype often has methods declared on it, while other properties are declared in the constructor.
JavaScript’s native objects are built in a similar fashion. For instance:
- Objects made with the new Object() or syntax have a prototype at Object.prototype.
- Arrays made using the new Array() or [] syntax have their prototype set to Array.prototype.
- The same holds true for the rest of the predefined objects, like Date and RegExp.
All objects share the same prototype property, Object.prototype, which is null.
The prototype chain
Create an object literal in the browser’s debug console by typing
const myObject = {
city: "Madrid",
greet() {
console.log(`Greetings from ${this.city}`);
},
};
myObject.greet(); // Greetings from Madrid
The object has the city property and the greet() method. Type the object’s name followed by a period (. ), such as myObject., and the console will display a menu with all the object’s properties. In addition to city and greet, you’ll find a wide variety of other real estate options!
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf
Try accessing one of them:
myObject.toString(); // "[object Object]"
The code compiles and runs (albeit the function’s toString() name may be misleading).
Where do these ancillary traits come from, and what are they?
The prototype is an in-built property of every JavaScript object. Since the prototype is an object, it too will have a prototype, creating a chain of prototypes. After reaching a prototype with a value of null for its own prototype, the chain is broken.
If a particular property of an object is not discovered in the object itself, it will be looked for in the prototype. If the property is still not located, the next level of prototypes is investigated, and so on, until either the property is located or the depth of the hierarchy is exhausted, at which point undefined is returned.
Then, the browser responds to a call to myObject.toString() by
- searching myObject for the toString method.
- unable to locate it there, myObject’s prototype object is searched for a toString method.
- calls it up after finding it there.
Where can I get myObject’s original version? The Object.getPrototypeOf() method will tell us:
Object.getPrototypeOf(myObject); // Object { }
As its name implies, Object.prototype is the most fundamental prototype that all objects possess. Object.prototype has no prototype, hence it comes last in the list of prototypes:
In some cases, Object.prototype won’t be the object’s prototype. Give this a shot:
const myDate = new Date();
let object = myDate;
do {
object = Object.getPrototypeOf(object);
console.log(object);
} while (object);
// Date.prototype
// Object { }
// null
The method first generates a Date object, then iteratively logs each prototype it encounters along the way. In this case, we can see that myDate derives from the Date.prototype object, which in turn derives from Object.prototype.
You’re actually invoking a function defined on Date.prototype whenever you use a common method like myDate2.getMonth().
Classes
The above image will show you the OOP concept of Inheritance. In JS, The Class keyword was added to ES2016, allowing us to modify the prototype in a similar fashion to the aforementioned techniques. Programmers accustomed to object-oriented languages will like the JavaScript Class, but it serves the same purpose as the two preceding examples.
class Rectangle {
constructor(height, width) {
this.height = height
this.width = width
}
get area() {
return this.calcArea()
}
calcArea() {
return this.height * this.width
}
}
const square = new Rectangle(10, 10)
console.log(square.area) // 100
This is basically the same as:
function Rectangle(height, width) {
this.height = height
this.width = width
}
Rectangle.prototype.calcArea = function calcArea() {
return this.height * this.width
}
When an Object property is looked up, the associated function is run thanks to the getter and setter methods of the corresponding class. This is merely syntactic sugar that facilitates searching for and setting properties.
Wrapping up
Is it better to utilize the outdated constructor-based syntax or the modern class syntax? I don’t think this question has a concrete response. How you put it to use is what matters.
In the classes section of this post, I have only shown how to make a class that is a prototype for an inheritance class. The scope of this article does not permit further discussion of JavaScript classes. Examine MDN’s classes’ documentation for more information. Or maybe I’ll try to devote a whole article to the topic of educational institutions.
If you’ve gained a better grasp of prototypes thanks to this essay, a round of applause would be greatly appreciated.
Let me know in the comments if you’d rather I cover a different subject.
References
Follow me on GitHub: Madhusha Prasad