JavaScript Prototypes vs Classes
Learn what JavaScript prototypes, JavaScript classes, and JavaScript constructor functions are. We will also go over the Object.create and Object.getPrototypeOf methods.
Table of Contents 📖
- What is a JavaScript Prototype?
- Mimicking a Class with JavaScript Prototypes
- JavaScript Constructor Functions
- JavaScript Classes
- JavaScript Class vs Prototype
What is a JavaScript Prototype?
In JavaScript, objects have properties and a prototype. A prototype is another object that provides fallback properties. For example, lets create an empty object and use one of these fallback properties.
let emptyObject = {}; console.log(emptyObject.toString()); // [object Object]
In this instance, our JavaScript object has no properties. However, we can call the method toString on it. This is because when a property that doesn't exist is accessed on an object, its prototype will be searched for the property. We can check what the prototype of an object is by using the method Object.getPrototypeOf.
let myPrototype = Object.getPrototypeOf(emptyObject); console.log(myPrototype); // {constructor: f Object(), toString: f toString()...}
The getPrototypeOf method returns the prototype of the provided object. We did not explicitly define the prototype of emptyObject but the Object prototype was still listed as it is the entity behind almost all objects in JavaScript. We can get the name of the Object prototype by calling the constructor.name property of it.
let myPrototype = Object.getPrototypeOf(emptyObject); console.log(myPrototype.constructor.name); // Object
We can explicitly set the prototype of an object using the Object.create method.
let parentObject = {}; let childObject = Object.create(parentObject); const childPrototype = Object.getPrototypeOf(childObject); console.log(childPrototype); // {}
The Object.create method takes an object as an argument to be used as the prototype. We used this method to set the parentObject to be the prototype of the childObject. As the parentObject is empty, what is printed is an empty object. However, if we give it a property it will be printed.
let parentObject = { inheritedProperty: 'This is an inherited property' }; let childObject = Object.create(parentObject); const childPrototype = Object.getPrototypeOf(childObject); console.log(childPrototype); // {inheritedProperty: 'This is an inherited property'}
Prototypes involve inheritance, therefore, if the object's prototype doesn't have the property, then the prototype's prototype is checked for the property and so on. This is why we can still call toString on the childObject. Because it inherits from the parentObject which inherits from the Object prototype.
console.log(childObject.toString()); // [object Object]
If we call a property that isn't present on an object or any of its prototypes then we get an type error.
const myErrorObject = {}; myErrorObject.sayHello(); // myErrorObject.sayHello is not a function myErrorObject.myProperty; // undefined
A few other common prototypes besides the Object prototype are the Function prototype and the Array prototype. The Function prototype provides default properties to functions and the Array prototype provides default properties to arrays.
const arrayPrototype = Object.getPrototypeOf([]); console.log(arrayPrototype.constructor); // Array() const functionPrototype = Object.getPrototypeOf(Math.trunc); console.log(functionPrototype.constructor); // Function()
Mimicking a Class with JavaScript Prototypes
Before classes were implemented in JavaScript the prototype keyword and methods were used. For example, we can create a class using a function, a prototype, and the Object.create method.
const pizzaPrototype = {
slice() {
console.log(The ${this.type} pizza is being sliced!
)
}
}
function makePizza(type) {
let pizza = Object.create(pizzaPrototype);
pizza.type = type;
return pizza;
}
const cheesePizza = makePizza('cheese'); cheesePizza.slice(); // The cheese pizza is being sliced!
const pepperoniPizza = makePizza('pepperoni'); pepperoniPizza.slice(); // The pepperoni pizza is being sliced!
Here, we attach the slice property to the pizza prototype. This is because we want all pizza objects to have the slice property. It is common to add properties to the prototype where all instances of it will have the same value, which is often methods. Individual properties, such as the type of pizza, will be stored on the object directly. This is why we specify the type of pizza on the object returned from Object.create. Then we create two pizza objects, a cheese pizza and a pepperoni pizza and call the slice prototype method.
JavaScript Constructor Functions
Next, JavaScript introduced constructor functions to make object creation easier. A constructor function is a function that creates and initializes an object. By convention, constructor functions start with a capital letter.
function Pizza(type) { this.type = type; }
Constructor functions are invoked by using the new keyword. The new keyword is what treats the function as a constructor. In other words, the new keyword creates an object, assigns its prototype, binds this to it, and returns it.
function Pizza(type) { this.type = type; } const cheesePizza = new Pizza('cheese'); console.log(cheesePizza.type); // cheese
If we want to add a method to the Pizza function, we use its prototype. Remember, prototypes are great for defining properties that are shared among all instances of a class. We want all our pizzas to have the slice method.
function Pizza(type) {
this.type = type;
}
Pizza.prototype.slice = function() {
console.log(The ${this.type} pizza has been sliced!
)
}
const cheesePizza = new Pizza('cheese');
cheesePizza.slice(); // The cheese pizza has been sliced!
We can see the prototype of the pizza object by using the Object.getPrototypeOf method.
const cheesePrototype = Object.getPrototypeOf(cheesePizza); console.log(cheesePrototype); // {slice: f, constructor: f, [[Prototype]]: Object}
Present in the prototype of the pizza object are the slice and constructor functions, we can also see the Object prototype, meaning we can call toString etc. on the pizza object. From this code, we can see the the JavaScript prototype system is a different take on classes. All a class is, is a blueprint for an object.
JavaScript Classes
It wasn't until ES6 (2015), that the class keyword was introduced to JavaScript. The class keyword allows us to define a constructor and methods in one place as opposed to writing a constructor function and adding properties to the prototype.
class Pizza { constructor(type) { this.type = type; }
slice() {
console.log(`The ${this.type} pizza is being sliced!`);
}
} const cheesePizza = new Pizza('cheese'); cheesePizza.slice(); // The cheese pizza is being sliced
Instead of using a constructor function, we use the constructor keyword. It is the constructor keyword that mimicks the constructor function. The other methods mimick attaching the method to the prototype.
JavaScript Class vs Prototype
At the end of the day, JavaScript classes are just constructor functions with a prototype property. The introduction of the ES6 class keyword just allows for a cleaner way to define classes.