How JavaScript Prototype Really Works
In this next article on JavaScript Techniques, we’ll be looking into the illusive JavaScript prototype object and how it can be used to create inheritance.
In the last article we took an in-depth look into constructor functions and how they can be considered as Classes from object-oriented languages. However, JavaScript is not a class-based language, but a prototype-based language. What does this mean exactly? Simply put, instead of using classes to define objects, JavaScript uses objects to create other objects.
JavaScript: a Prototype-based Language
In a traditional class-based language (such as Java or C++), to create an object you must first create a class which defines that objects properties and methods. The class is used as a blueprint to know how to construct the object, just in a similar manner to how a house is constructed by following an actual blueprint.
Once the class is written, you can use it to create an instance of that class. An instance of a class is a fancy way of saying that you created an object from the class by using the new
keyword. The object will have a copy of every property and method that was defined in the class. Class-based languages use classes to define objects.
public class Shape {
private int height;
private int width;
public Shape(int h, int w) {
height = h;
width = w;
}
public int area() {
return height * width;
}
}
Shape shape = new Shape(10, 2);
System.out.println(shape.area()); // 20
In JavaScript, there are no classes, only objects and primitive data types. For the sake of simplicity, we’ll just say that everything in JavaScript is an object, from numbers, to strings, and even functions. As discussed in the previous article, there are two ways to create an object: by using an object literal or by using a constructor function. We’ll stick to just constructor functions for the purposes of this article.
To create an object you first create a function
object (a.k.a. a constructor function). The function
object is then used as a blueprint to construct a new object by using the new
keyword. The object will have a copy of every property and method that was defined in the function
object. Prototype-based languages use objects to create other objects.
function Shape() {
this.height = 10;
this.width = 10;
this.area = function() {
return this.height * this.width;
};
};
var shape = new Shape();
console.log(shape.area()); // 100
However, there are many properties and functions that an object has access to which are not defined in their constructor function. Take the toString()
function for example. Any object can use the toString()
function to return a string of itself. This function was not defined in the Shape()
function, yet the shape
object can still access it.
console.log(shape.toString()); // "[object Object]";
So where did the object get this function? It got it from the JavaScript prototype.
The JavaScript Prototype
Every object in JavaScript has a (mostly) hidden property called [[Prototype]]
. The [[Prototype]]
property is a pointer that references the object to which JavaScript will pass delegation to if a property is not found in the current object. Delegation means that if you are unable to do something, you’ll tell someone else to do it for you.
When you ask an object for a property, JavaScript will first look at the object to see if it has that property. If the object doesn’t have the specified property, JavaScript will look at the [[Prototype]]
property and see if the object that it points to has the specified property. This continues until either the property is found or the [[Prototype]]
property results in null
. Linking objects in this way is known as the prototype chain, with each object in the chain being a link. In most browsers, the [[Prototype]]
property of an object can be accessed by using the non-standard __proto__
property, but it can always be accessed by using Object.isPrototypeOf()
.
Let’s go back to our shape.toString()
example. when we ask the shape
object for a property called toString
, JavaScript first looks at the shape
object and sees that it doesn’t have a property called toString
. So JavaScript will look at the shape’s [[Prototype]]
property which points to another object. Looking at this object, JavaScript sees that it too doesn’t have a property called toString
, so looks at this object’s [[Prototype]]
property which points to yet another object. Looking at this new object, JavaScript finds that it does has a property called toString
, which JavaScript then uses and end it’s search through the prototype chain.
We can see that this is true by using the hasOwnProperty()
function that all objects have. The hasOwnProperty()
function returns true if the object has the specified property, otherwise it returns false.
console.log(shape.hasOwnProperty('toString')); // false
console.log(shape.__proto__.hasOwnProperty('toString')); // false
console.log(shape.__proto__.__proto__.hasOwnProperty('toString')); // true
So how did the shape object become attached to this prototype chain? We didn’t set it up anywhere. It turns out that JavaScript will automatically attach any object that is created to a prototype chain. How does it know how to do this? It knew by looking at the Shape
function’s function.prototype
property.
The Function.prototype
Every function in JavaScript has a visible property called the function.prototype
. The function.prototype
is not the same as the [[Prototype]]
property of all objects, but they are not completely unrelated. The function.prototype
is a pointer that points to the object that will be used as the [[Prototype]]
property for all new objects created from the function.
When you create an object from a function using the new
keyword, JavaScript looks at the function.prototype
property of the function and sets the new object’s [[Prototype]]
property to the object that the function.prototype
points to. By using the function.prototype
, JavaScript is able to know how to link all new objects into a prototype chain. The function.prototype
property of a function can be accessed by using the prototype
property.
So how does the function.prototype
get set for a function? Again, we didn’t set this up but JavaScript did it for us. When you create a function, JavaScript creates a new object from the function. This object has no methods or properties of its own (except for the [[Prototype]]
property), and is solely used as a link in the prototype chain. For lack of a better term, I will refer to this object as the empty object of the function. After the empty object is created, JavaScript sets the function.prototype
of the function to its empty object, thus setting up the information it needs for the prototype chain.
Going back to our Shape
example, when the Shape
function is first created, JavaScript creates an empty object from the Shape
function. JavaScript then sets the function.prototype
of the Shape
function to the empty Shape
object. When you create a new object from the Shape
function by using the new
keyword, JavaScript looks at the Shape
function’s function.prototype
property (which points to the empty Shape
object) and sets the new shape object’s [[Prototype]]
property to this object.
We can see that the shape
object points to the Shape
function’s function.prototype
by using a strict comparison. A strict comparison returns true if both objects are the same object.
function Shape() {
this.height = 10;
this.width = 10;
this.area = function() {
return this.height * this.width;
};
};
console.log(Shape.prototype); // Shape {}
var shape = new Shape();
console.log(shape.__proto__); // Shape {}
console.log(shape.__proto__ === Shape.prototype); // true
You can manually change what the function.prototype
points to and thus how new objects are linked into a prototype chain. By doing so, you can create inheritance in JavaScript.
JavaScript Prototypal Inheritance
The JavaScript prototype chain is fundamental to inheritance. Inheritance in JavaScript doesn’t work like it does in a class-based language. In a class-based language, an object created from a class which inherits from another class (child classes and parent classes respectively) will have a copy of every property and method found in both classes.
In a prototype-based language, there are no child or parent objects. Instead, an object only copies the properties and methods from the function that created it and relies on the prototype chain to fill in the gaps for missing properties and methods. This is known as prototypal inheritance.
To implement inheritance, we first create two functions, one of which will inherit from the other.
function Shape() {
this.height = 10;
this.width = 10;
this.area = function() {
return this.height * this.width;
};
};
// The Triangle function will inherit from the Shape function
function Triangle() {
this.area = function() {
return this.height * this.width / 2;
}
}
We then set the function.prototype
property of one function to an instance of the other function. This will ensure that all new objects created from the “child” function will have access to all the properties of the “parent” object through the prototype chain.
Triangle.prototype = new Shape();
var triangle = new Triangle();
console.log(triangle.height + ", " + triangle.width); // 10, 10
console.log(triangle.area()); // 50
In this example, triangle
does not have a property called height
or width
so JavaScript looks at the prototype chain to find them, which it does in the shape
object. When we ask for triangle.area()
, we are given the triangle
object’s area()
function and not the shape
object’s area()
function because there was no need to look at the prototype chain since the triangle
object has that property.
Setting New Properties
The great thing about prototypal inheritance is that is it very easy to give objects new properties that all objects further down in the prototype chain can access. Let’s say we wanted to add a new function to Shape
that would calculate the perimeter. We could try to add it to the Shape()
function itself, but as we saw in the previous article this didn’t work.
function Shape() {
this.height = 10;
this.width = 10;
this.area = function() {
return this.height * this.width;
};
};
var shape = new Shape();
console.log(shape.perimeter()); // TypeError: Object # has no method 'perimeter'
Shape.perimeter = function() {
return this.height * 2 + this.width * 2;
};
var shape1 = new Shape();
console.log(shape1.perimeter()); // TypeError: Object # has no method 'perimeter'
What we have to do instead is add the function to the function.prototype
directly. By adding the function into the function.prototype
, it is added into the prototype chain and therefore accessible by any object created from the Shape()
function.
Shape.prototype.perimeter = function() {
return this.height * 2 + this.width * 2;
}
console.log(shape.perimeter()); // 40
So why did adding it to the function not work even for new objects created from the function? Any property added to a function after it has been created act as static properties of the function. This means that you can only gain access to the property by first accessing the object (i.e. Shape.perimeter()
). Being a static property also means that it doesn’t get added to any new objects created from the function. So they only way to add new properties to functions is to go through the function.prototype
.
Conclusion
The JavaScript prototype can be a bit confusing to those who are new to JavaScript or are familiar with class-based languages. However, understanding the JavaScript prototype is fundamental to understanding how JavaScript works as a prototype-based language. By linking objects together into prototype chains, JavaScript can resolve properties that are not found in an object.
In the next article, we’ll take a look at some of the gotchas of JavaScript and the JavaScript prototype, as well as a different, cleaner way to create prototype chains.