JavaScript uses prototype-based inheritance, which is slightly different than inheritance in other languages. Suppose you create a new vanilla JavaScript object:
const obj = new Object();
obj instanceof Object; // true
obj.constructor === Object; // true
The instanceof
operator tells you whether a given object is an instance of a given JavaScript class. You can also use
instanceof
with your own custom class:
class MyClass {}
const obj = new MyClass();
obj instanceof MyClass; // true
obj instanceof Object; // true
obj.constructor === MyClass; // true
The prototype
Property
In JavaScript, every class has a prototype
property. A class' prototype
is an object. For example,
MyClass.prototype
is an object:
MyClass.prototype; // MyClass {}
JavaScript has a built-in Object.getPrototypeOf()
function that lets you get
the prototype
of an object.
const obj = new MyClass();
Object.getPrototypeOf(obj) === MyClass.prototype; // true
In JavaScript, checking if an object obj
is an instance of a given class C
(excluding inheritance) is equivalent to checking if Obj.getPrototypeOf(obj) === C.prototype
.
Setting Prototype Properties
Prototypes are just JavaScript objects. When you access a property on an object,
like accessing obj.foo
, JavaScript first looks to see if obj
has a foo
property using hasOwnProperty()
. If it doesn't, JavaScript then checks the constructor's prototype
for a foo
property. For example:
const obj = new MyClass();
// If you set `foo` on `obj`'s constructor's prototype, that means you can
// access `obj.foo`.
MyClass.prototype.foo = 42;
obj.foo; // 42
// `foo` is an _inherited property_ as opposed to an _own property_.
obj.hasOwnProperty('foo');
The Prototype Chain
So instances of MyClass
point up to MyClass.prototype
, and checking whether
an object is an instance of MyClass
is defined as checking whether Object.getPrototypeOf()
returns MyClass.prototype
. Where does inheritance
come in?
The trick is that, since MyClass.prototype
is an object, Object.getPrototypeOf(MyClass)
points to the prototype of the base class Object
.
Object.getPrototypeOf(MyClass.prototype) === Object.protype; // true
If you use the extends
keyword for inheritance, Object.getPrototypeOf()
helps you
get the base class's prototype:
class BaseClass {}
class SubClass extends BaseClass {}
Object.getPrototypeOf(SubClass.prototype) === BaseClass.prototype; // true
Object.getPrototypeOf(SubClass.prototype) === Object.prototype; // false
// Since `BaseClass` implicitly inherits from object:
Object.getPrototypeOf(BaseClass.prototype) === Object.prototype;
Surprisingly enough, a class's prototype
has a constructor
property that
points to the class itself. So to get the BaseClass
constructor from SubClass
,
you should get the prototype's constructor
:
Object.getPrototypeOf(SubClass.prototype).constructor === BaseClass; // true
Given any class, you can walk up the inheritance tree and end up at Object.prototype
. The prototype chain is the idea that you can follow
a list of prototypes to get every class in an inheritance hierarchy.
class BaseClass {}
class SubClass extends BaseClass {}
let curPrototype = SubClass.prototype;
// Prints "SubClass", "BaseClass", "Object"
while (curPrototype != null) {
console.log(curPrototype.constructor.name);
curPrototype = Object.getPrototypeOf(curPrototype);
}
For example, you can implement your own instanceof
operator by walking
up the prototype chain yourself. Given v instanceof C
, The instanceof
operator
walks up v
's prototype chain to see if it can find C.prototype
.
function isInstanceOf(obj, Class) {
const prototypeToFind = Class.prototype;
let curPrototype = obj.constructor.prototype;
while (curPrototype != null) {
if (curPrototype === prototypeToFind) {
return true;
}
curPrototype = Object.getPrototypeOf(curPrototype);
}
return false;
}
Moving On
JavaScript prototypes can be confusing, but ES6 classes and
Object.getPrototypeOf()
have done a great job in making prototype-based
inheritance more comprehensible. Iterating up the prototype chain is just
a simple while()
loop with Object.getPrototypeOf()
, and you can easily
access the actual class using .constructor
.