DEV Community

arnav-aggarwal
arnav-aggarwal

Posted on

Master JavaScript Prototypes & Inheritance

This article is taken from my course, Step Up Your JS: A Comprehensive Guide to Intermediate JavaScript.

Inheritance

Inheritance refers to an object’s ability to access methods and other properties from another object. Objects can inherit things from other objects. Inheritance in JavaScript works through something called prototypes and this form of inheritance is often called prototypal inheritance.

In this article, we’ll cover a lot of seemingly unrelated topics and tie them together at the end. There’s also a TL;DR at the end for those who want the short version.

Object, Array, and Function

JavaScript gives us access to three global functions: Object, Array, and Function. Yes, these are all functions.

console.log(Object); // -> ƒ Object() { [native code] }
console.log(Array); // -> ƒ Array() { [native code] }
console.log(Function); // -> ƒ Function() { [native code] }
Enter fullscreen mode Exit fullscreen mode

You don’t know it, but every time you create an object literal, the JavaScript engine is effectively calling new Object(). An object literal is an object created by writing {}, as in var obj = {};. So an object literal is an implicit call to Object.

Same goes for arrays and functions. We can think of an array as coming from the Array constructor and a function as coming from the Function constructor.

Object Prototypes

__proto__

All JavaScript objects have a prototype. Browsers implement prototypes through the __proto__ property and this is how we’ll refer to it. This is often called the dunder proto, short for double underscore prototype. Don’t EVER reassign this property or use it directly. The MDN page for __proto__ warns us in big red blocks to never do this.

prototype

Functions also have a prototype property. This is distinct from their __proto__ property. This makes discussion rather confusing, so I’ll spell out the syntax I’ll be using. When I refer to a prototype and the word “prototype isn’t highlighted grey, I’m referring to the __proto__ property. When I use prototype in grey, I’m talking about a function’s prototype property.

If we were to log the prototype of an object in Chrome, this is what we’d see.

var obj = {};
console.log(obj.__proto__);
// -> {constructor: ƒ, __defineGetter__: ƒ, …}
Enter fullscreen mode Exit fullscreen mode

The __proto__ property is a reference to another object that has several properties on it. Every object literal we create has this __proto__ property pointing to this same object.

There are a couple of important points:

  • The __proto__ of an object literal is equal to Object.prototype

  • The __proto__ of Object.prototype is null

We’ll explain why soon.

The Prototype Chain

To understand object prototypes, we need to discuss object lookup behavior. When we look for a property of an object, the JavaScript engine will first check the object itself for the existence of the property. If not found, it’ll go to the object’s prototype and check that object. If found, it’ll use that property.

If not found, it’ll go to the prototype’s prototype, and on and on until it finds an object with a __proto__ property equal to null. So if we were to attempt to look up the property someProperty on our obj object from above, the engine would first check the object itself.

It wouldn’t find it and would then jump to its __proto__ object which is equal to Object.prototype. It wouldn’t find it there either and upon seeing that the next __proto__ is null, it would return undefined.

This is called the prototype chain. It’s normally described as a chain going downwards, with null at the very top and the object we’re using at the bottom.

When performing a lookup, the engine will traverse up the chain looking for the property and return the first one it finds, or undefinedif it’s not present in the prototype chain.

__proto__ === null
|
|
__proto__ === Object.prototype
|
|
{ object literal }
Enter fullscreen mode Exit fullscreen mode

This can be demonstrated. Here we’re going to work with __proto__ directly for the purpose of demonstration. Again, don’t ever do it.

var obj = {};
obj.__proto__.testValue = 'Hello!';

console.log(obj); // -> {}
console.log(obj.testValue); // -> Hello!
Enter fullscreen mode Exit fullscreen mode

This prototype chain is depicted below.

__proto__ === null
|
|
__proto__ === Object.prototype -> testValue: 'Hello!'
|
|
obj
Enter fullscreen mode Exit fullscreen mode

When we log obj, we get an empty object because the property testValue isn’t present directly on the object. However, logging obj.testValue triggers a lookup. The engine goes up the prototype chain and finds testValue present on the object’s prototype and we see that value printing out.

hasOwnProperty

There’s a method available on objects called hasOwnProperty. It’ll return true or false based on whether the object itself contains the property being tested. Testing for __proto__, however, will ALWAYS return false.

var obj = {};
obj.__proto__.testValue = 'Hello!';

console.log(obj.hasOwnProperty('testValue'));
// -> false

console.log(obj.__proto__.hasOwnProperty('testValue'));
// -> true
Enter fullscreen mode Exit fullscreen mode

Function prototypes

As mentioned, functions all have a prototype property distinct from their __proto__ property. It’s an object. A function’s prototype's __proto__ property is equal to Object.prototype. In other words:

function fn() {}
console.log(fn.prototype.__proto__ === Object.prototype);
// -> true
Enter fullscreen mode Exit fullscreen mode

Function Prototypes and 'new'

A function’s prototype property shows its usefulness in object oriented programming. When we invoke a function using new, the object bound to this in the constructor function is special. The new keyword sets the object’s __proto__ to be the prototype property of the constructing function.

When we call a function with new, it sets the returned object’s __proto__ property equal to the function’s prototype property. This is the key to inheritance.

We’ve assembled a few points so far:

  • The __proto__ of an object created by calling a function with new is equal to the prototype of that function

  • The __proto__ of a function’s prototype is equal to Object.prototype

  • The __proto__ of Object.prototype is null

This lets us assemble the following prototype chain.

function Fn() {}
var obj = new Fn();

console.log(obj.__proto__ === Fn.prototype);
// -> true

console.log(obj.__proto__.__proto__=== Object.prototype);
// -> true

console.log(obj.__proto__.__proto__.__proto__ === null);
// -> true
Enter fullscreen mode Exit fullscreen mode

Visually drawn:

__proto__ === null
|
|             
__proto__ === Object.prototype
|
|
__proto__ === Fn.prototype
|
|
obj
Enter fullscreen mode Exit fullscreen mode

Implementing Inheritance

We can work with a function’s prototype property directly and safely. By placing methods and other properties on a function’s prototype, we enable all objects created by that function (using new) to access those properties through inheritance.

function Fn() {}

Fn.prototype.print = function() {
    console.log("Calling Fn.prototype's print method");
};

var obj = new Fn();
obj.print(); // -> Calling Fn.prototype's print method
Enter fullscreen mode Exit fullscreen mode

You might be wondering what the point of this is. We can just attach this method inside the constructing function itself, like this.

function Fn() {
    this.print = function() {
        console.log("Calling the object's print method");
    };
}

var obj = new Fn();
obj.print(); // -> Calling the object's print method
Enter fullscreen mode Exit fullscreen mode

You’re right, this works. The difference is that this way, each object created by calling new Fn() will have its own version of print placed directly on the object. They’ll be distinct functions in memory. The problem with this is performance and memory usage.

Performance

There may be times when you need thousands of new objects created from a constructor function. Using this second way of attaching print, we now have thousands of copies of print, each one attached to one of the objects.

Using the prototype chain, no matter how many objects we create out of Fn, we have one print sitting on Fn.prototype.

One method is not a big deal. Large programs, however, often have tens of methods that objects need. If an object needs access to 20 methods and we create 100,000 objects, the JavaScript engine has created 2,000,000 new functions.

If this needs to happen multiple times, this will cause noticeable speed and memory issues. Compare this to having a total of 20 functions and giving each object the ability to use the same functions through the prototype chain. Much more scalable.

Using console.time and console.timeEnd, we can directly show the difference in how long it takes. Here’s the time difference of creating 2 million objects with functions directly on them vs. on the prototype. We’re storing all the objects in an array.

Creating new functions (left) vs. using prototypal inheritance (right)Creating new functions (left) vs. using prototypal inheritance (right)

As we can see, putting the print method on the prototype takes about half the time.

__proto__ of Literals

As mentioned, an object’s __proto__ is equal to the prototype of the function that created the object. This rule applies to literals also. Remember that object literals come from Object, arrays come from Array, and functions come from Function.

var obj = {};
var arr = [];
function fn() {}

console.log(obj.__proto__ === Object.prototype); // -> true
console.log(arr.__proto__ === Array.prototype); // -> true
console.log(fn.__proto__ === Function.prototype); // -> true
Enter fullscreen mode Exit fullscreen mode

We can now explain why we’re able to call methods on arrays and objects. If we have an array arr, we can call arr.map() because the method map is present on Array.prototype. We can call obj.hasOwnProperty() because hasOwnProperty is present on Object.prototype. We’ve been using inheritance the whole time and didn’t even know it.

The end of the __proto__ chain of both Array and Function is equal to Object.prototype. They all derive from the same thing. This is why arrays, functions, and objects are all considered first-class objects in JavaScript.

constructor

We’ve thrown the word constructor around a few times. Let’s explain what it is. Every function’s prototype has a constructor property on it that points back to the function itself. This is something the engine does for every function.

function Fn() {}
console.log(Fn.prototype.constructor === Fn);
// -> true
Enter fullscreen mode Exit fullscreen mode

An object created by running new Fn() will have its __proto__ equal to Fn.prototype. So if we were to attempt to log the constructor property of that object, the engine would give us Fn through its lookup process.

function Fn() {}
var obj = new Fn();
console.log(obj.constructor); // -> ƒ Fn(){}
Enter fullscreen mode Exit fullscreen mode

Why it’s Useful

The constructor property on an object is useful because it can tell us how an object was created. Logging the constructor property directly on an object will tell us exactly which function created our object.

function Fn() {};

var normalObj = {};
var fnObj = new Fn();

console.log(normalObj.constructor);
// -> ƒ Object() { [native code] }

console.log(fnObj.constructor);
// -> ƒ Fn() {}
Enter fullscreen mode Exit fullscreen mode

Object.create

There’s a way to set the prototype of an object manually. Object.create. This function will take in an object as a parameter. It’ll return a brand new object whose __proto__ property is equal to the object that was passed in.

var prototypeObj = {
    testValue: 'Hello!'
};

var obj = Object.create(prototypeObj);
console.log(obj); // -> {}
console.log(obj.__proto__ === prototypeObj); // -> true
console.log(obj.testValue); // -> 'Hello!'
Enter fullscreen mode Exit fullscreen mode

This gives us an easy way to extend the prototype chain. We can make objects inherit from any object we like, not just a function’s prototype.

If you’d like more information and examples, the MDN page for Object.create is a great resource.

Phew.

That was a lot. I know. However, you now have a deep understanding of inheritance in JavaScript.

Prototypes Summary

In short, inheritance in JavaScript is implemented through the prototype chain. Every normally created object, array, and function has a prototype chain of __proto__ properties ending with Object.prototype at the top. This is why they’re all considered first-class objects in JavaScript.

Functions have a prototype property in addition to the __proto__ property. When using a constructor function with new, it’s good practice to place methods on the function’s prototype instead of on the object itself. The returned object’s __proto__ will be equal to the function’s prototype so it will inherit all methods on the function’s prototype. This prevents unnecessary memory usage and improves speed.

We can check if an object has its own property by using the hasOwnProperty method. We can manually set up inheritance by using Object.create.

That’s It. If this was helpful, please hit the heart so this story reaches more people. Also feel free to check out my other work.

My Work

Online Course

I’ve created an online course covering intermediate JavaScript topics such as scope, closures, OOP, this, new, apply/call/bind, asynchronous code, array and object manipulation, and ES2015+.
Step Up Your JS: A Comprehensive Guide to Intermediate JavaScript

Recent Articles

Explaining Value vs. Reference in Javascript
React Ecosystem Setup — Step-By-Step Walkthrough

Top comments (1)

Collapse
 
alexthejewell profile image
Alex Jewell

Thanks for this great explanation.
Being new to Javascript this was an area I've had trouble figuring out.