Skip to main content

Class prototype

ES6 brings us an easy way to project abstractions with a paradigm known for people with C++/Java experience — classes:

class Shape {}
class Circle extends Shape {}

Classes were introduced to specification years ago and an old, more "native" way to inherit one object from another by using a prototype chain becomes something similar to ancient Egypt hieroglyphs, especially for developers who started to learn language recently:

function Shape() {}
function Circle() {}

Circle.prototype = Object.create(Shape.prototype);

I would like to refer the curious reader to MDN for further information about this way of inheritance. I want to mention only one thing: an inheritance with classes also based on a prototype chain. So we can say that both code examples are similar. But there are nuances.

Prototype property #

Suppose that you have created two classes and want to try to extend one from another with the last one way:

class Shape {}
class Circle extends Shape {}

Circle.prototype = Object.create(Shape.prototype);

The class actually is function with the constructor body and this way should work, right? But nope: when you will add some properties to Shape prototype, it won't be exposed to instances of Circle for some reason:

Shape.prototype.calcArea = function calcArea() {}

new Circle().calcArea(); // <...>.getArea is not a function

Why it's happening? #

The reason for this behaviour is hidden behind the property descriptor associated with prototype. If you will take a look at it for a class you will notice that this property is not writable. That means that you cannot change the value of the prototype of the class by default. Opposite that, a prototype of the function is writable.

Object.getOwnPropertyDescriptor(class {}, 'prototype')
/*
* {
* value: {},
* writable: false,
* enumerable: false,
* configurable: false
* }
*/


Object.getOwnPropertyDescriptor(function() {}, 'prototype')
/*
* {
* value: {},
* writable: true,
* enumerable: false,
* configurable: false
* }
*/

This behaviour is described in the algorithm of evaluation of a class definition in the specification. According to the 12th step of the algorithm, an abstract operation MakeConstructor is executed which receives writablePrototype property with a false value.

And, as you can already guess, for all ways to instantiate a new function — declaration, expression or function constructor — operation MakeConstructor is called without passing to it writableProperty, which, by default, receives a true value.