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] }
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__: ƒ, …}
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.prototypeThe
__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 undefined
if it’s not present in the prototype chain.
__proto__ === null
|
|
__proto__ === Object.prototype
|
|
{ object literal }
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!
This prototype chain is depicted below.
__proto__ === null
|
|
__proto__ === Object.prototype -> testValue: 'Hello!'
|
|
obj
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
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
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 withnew
is equal to theprototype
of that functionThe
__proto__
of a function’sprototype
is equal toObject.prototype
The
__proto__
ofObject.prototype
isnull
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
Visually drawn:
__proto__ === null
|
|
__proto__ === Object.prototype
|
|
__proto__ === Fn.prototype
|
|
obj
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
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
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)
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
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.prototyp
e. 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
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(){}
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() {}
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!'
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)
Thanks for this great explanation.
Being new to Javascript this was an area I've had trouble figuring out.