White Tower Summoning: Mimicking C# Classical Inheritance in JavaScript

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

In this article we are going to take a deep-dive into how to emulate classical inheritance in JavaScript and get to the nearest equivalent to what you are accustomed to in C#. We will focus in the alternatives we had prior to ES6 classes so that you can work with classes even if you are stuck in ES5 and so that you can understand the underlying implementation of ES6 classes which are just syntactic sugar over the JavaScript prototypical inheritance model.

We will start by emulating a single C# class in JavaScript and attempt to find equivalents to C# constructs like access modifiers, static classes and method overloading. We will then continue by expanding our knowledge from a single class to many, in order to arrive to something similar to C# classical inheritance. Something that will let you work in JavaScript just like you usually do in C#, building class taxonomies and working with instances of classes.

Can I Just Jump Over to ES6 Classes Directly?

In this article we are going to focus completely in learning how you can implement classes and classical inheritance in JavaScript without using ES6 classes. This is important because it will teach you how to do a class equivalent in ES5, what lies behind ES6 classes and how ES6 classes relate to the rest of JavaScript.

Emulating a C# Class in JavaScript

In order to create the equivalent of a class in JavaScript you need to combine a constructor function with a prototype. Let’s do a quick review of each of these constructs and see how combining them results in something similar to a C# class.

I am going to be using the word class a lot and I am going to be referring to a constructor function and prototype pair, the equivalent to C# classes in JavaScript prior to ES6.

I won’t refer to ES6 classes unless I say ES6 classes.

Constructor Functions

You can experiment with all examples in this article directly within this jsBin.

Constructor functions allow us to create objects that share the same properties. They work as a recipe for object creation and represent a custom type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Barbarian(name){
    this.name = name;
    this["character class"] = "barbarian";
    this.hp = 200;
    this.weapons = [];
    this.talks = function(){
        console.log("I am " + this.name + " !!!");
    };
    this.equipsWeapon = function(weapon){
        weapon.equipped = true;
        this.weapons.push(weapon);
    };
    this.toString = function(){
        return this.name;
    };
}

Notice how the constructor, not only initializes an object with a set of values like in C#, but it also determines which properties an object is going to have. This means that a constructor function works effectively as both a constructor and a class definition.

Having defined a constructor function you can create an instance by using the new keyword just like in C#:

1
2
3
4
5
6
7
8
let conan = new Barbarian("Conan, the Barbarian");
conan.equipsWeapon({
        name: "two-handed sword",
        type: "sword",
        damage: "2d20+10",
        material: "cimmerian steel",
        status: "well maintained"
    });

This new instance is of type Barbarian and all its properties are publicly available:

1
2
3
4
5
6
7
8
9
10
console.log(`Conan is a Barbarian: ${conan instanceof Barbarian}`);
// => Conan is a Barbarian: true
console.log(`Conan is an Object: ${conan instanceof Barbarian}`);
// => Conan is an Object: true
conan.talks();
// => I am Conan, the Barbarian!!!
console.log(conan.name);
// => Conan, The Barbarian"
console.log(`Conan has these weapons: ${conan.weapons}`);
// => Conan has these weapons: two-handed sword

Prototypical Inheritance

In previous articles of these series we saw how inheritance in JavaScript is slightly different than what we are accustomed to in C#. For one, there are no classes. Furthermore, inheritance doesn’t play as big a part in polymorphism since JavaScript is a dynamically typed language that relies in duck typing.

JavaScript is all about objects, and achieves inheritance not via class inheritance but via prototypical inheritance, that is, objects that inherit from other objects which act as prototypes (a sort of blueprint).

Prototypes

Every constructor function (and every function) in JavaScript has a prototype property. This property holds an object that will act as a prototype – will provide shared properties – for all objects created by calling the constructor function with the new keyword. The prototype object also has a constructor property that points back to the constructor function:

1
2
3
4
5
6
7
// every function has a prototype property
console.log(`Barbarian.prototype: ${Barbarian.prototype}`);
// => Barbarian.prototype: [object Object]

// and the prototype has a constructor property that points back to the function
console.log(`Barbarian.prototype.constructor: ${Barbarian.prototype.constructor}`);
// => Barbarian.prototype.constructor: function Barbarian(name) {...}

We can verify how all objects instantiated using that constructor function will inherit properties and methods from the prototype object. If we take the prototype from the previous example, we extend it with a simple saysHi function and we instantiate two rough barbarians like this:

1
2
3
4
5
6
7
Barbarian.prototype.saysHi = function (){ console.log("Hi! I am " + this.name);}
var krull = new Barbarian("krull");
krull.saysHi();
// => Hi! I am krull
var conan = new Barbarian("Conan");
conan.saysHi();
// => Hi! I am Conan

We can appreciate how both objects krull and conan expose the saysHi method even though it wasn’t part of the Barbarian constructor function. This is possible due to the prototype chain existing between object (krull) and prototype (Barbarian.prototype) which allows the object to delegate method calls to the prototype.

A common idiom to avoid the need to write the Function.prototype.property each time you want to augment the prototype is to assign the prototype to a new object:

1
2
3
4
Barbarian.prototype = {
  constructor: Barbarian
  saysHi: function(){console.log("Hi! I am " + this.name);}
}

So this that you saw was the simplest JavaScript inheritance scenario. In this scenario you have an object (krull) that inherits properties from another object (Barbarian.prototype). In a more common scenario you may have several custom prototypes in your inheritance hierarchy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Inheritance Hierarchy:
//   Barbarian -> DrawableGameObject -> GameObject -> Object

function GameObject(){
    // common logic for a game object within a game engine
    // like being part of a game loop
}

function DrawableGameObject(){
    // common logic for a game object that can be drawn in a screen
}
DrawableGameObject.prototype = Object.create(GameObject.prototype);
DrawableGameObject.constructor = DrawableGameObject;

// function Barbarian(){}
// we change the prototype from the previous examples to now be a 
// new object that in turn has a DrawableGameObject prototype
Barbarian.prototype = Object.create(DrawableGameObject.prototype);
Barbarian.prototype.constructor = Barbarian;

In this example we use the Object.create function that we saw in previous articles to create a new object with a specific prototype. For instance, Object.create(GameObject.prototype) creates a new object that has GameObject.prototype as prototype, that is, the prototype of the GameObject custom type .

You can use the instanceof operator to verify that indeed Barbarian is a subtype of DrawableGameObject and GameObject:

1
2
3
4
5
6
7
var krom = new Barbarian();
console.log(`krom is Barbarian: ${krom instanceof Barbarian}`);
// => krom is Barbarian: true
console.log(`krom is DrawableGameObject: ${krom instanceof DrawableGameObject}`);
// => krom is DrawableGameObject: true
console.log(`krom is GameObject: ${krom instanceof GameObject}`);
// => krom is GameObject: true

Additionally, if you need to know whether a property is part of the object itself or part of the prototype you can use the hasOwnProperty method:

1
2
3
4
5
6
console.log(
  `conan has a property called name: ${conan.hasOwnProperty("name")}`);
// => conan has a property called name: true
console.log(
  `conan has a property called saysHi: ${conan.hasOwnProperty("saysHi")}`);
// => conan has a property called saysHi: false

Now that we have reviewed both constructor functions and prototypes let’s see how putting them together brings us nearer to C# inheritance model.

Constructor Function + Prototype = Class

The nearest equivalent to a C# class in JavaScript is a constructor function and a prototype pair. Each carry out different tasks:

  • The constructor function defines a custom type with a series of properties. It will determine which specific properties each instance of that custom type is going to have.
  • The prototype provides a series of methods that are shared across all instances of a given type. It is also the bridge between classes and the means to achieve class inheritance by connecting them through a prototype chain.

Putting these two together we can define a ClassyBarbarian class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// the constructor function:
//   - defines the ClassyBarbarian type
//   - defines the properties a ClassyBarbarian instance is going to have
function ClassyBarbarian(name){
    this.name = name;
    this["character class"] = "barbarian";
    this.hp = 200;
    this.weapons = [];
}

// the prototype:
//   - defines the methods shared across all ClassyBarbarian instances
ClassyBarbarian.prototype = {
    constructor: ClassyBarbarian,
    talks: function(){
        console.log("I am " + this.name + " !!!");
    },
    equipsWeapon: function(weapon){
        weapon.equipped = true;
        this.weapons.push(weapon);
        console.log(`${this.name} grabs a ${weapon.name} from the cavern floor`);
    },
    toString: function(){
        return this.name;
    },
    saysHi: function (){
        console.log("Hi! I am " + this.name);
    }
};

And we can use it just like we would use a class in C#:

1
2
3
4
5
6
7
8
9
var logen = new ClassyBarbarian('Logen Ninefingers');
logen.saysHi();
// => Hi! I am Logen Ninefingers
logen.talks();
// => I am Logen Ninefingers !!!
logen.equipsWeapon({name:'very large axe'});
// => Logen Ningefingers grabs a very large axe from the cavern floor
console.log(logen.weapons.map(w => w.name));
// => ["very large axe"]

Access Modifiers

We don’t have access modifiers in JavaScript: public, protected and private do not exist. Every property that you add to an object is public and accessible by anyone that has access to that object. That being said there are two patterns that you can use to get something similar to private variables and methods and the benefits of information hiding. You’ve learned about them in previous articles:

  • Closures: You can use them to capture the value of variables from outer scopes (in this case the methods of a class are closures that capture variables defined within a constructor function). These variables are not part of the object itself, they are just captured by the object methods, and therefore are not accessible through the object.
  • ES6 Symbols: When you use symbols to index properties and methods in your objects, because a symbol is unique, you can only access these properties or methods if you have access to the symbol. When using a symbol the object has a property indexed by that symbol that is public, but even if you have access to the object, because you don’t have a reference to the symbol, you cannot access the property. 1

Let’s see how we can use each of these approaches with our JavaScript classes and the implications of either choice.

Classes and Information Hiding with Closures

In order to use closures to achieve data privacy you need to have a function that encloses a variable. This poses a small problem if we want to follow the constructor function for state plus prototype for behavior pattern. That’s because the prototype methods are defined outside of the constructor function and therefore cannot enclose any of the variables defined within it.

As a result, if we want to use closures to manage data privacy we need to move our methods from the prototype into the constructor function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// constructor function
function PrivateBarbarian(name){
    // private members
    var weapons = [],
        hp = 200;

    // public members
    this.name = name;
    this["character class"] = "private barbarian";
    this.equipsWeapon = function(weapon){
        weapon.equipped = true;
        // this function encloses the weapons variable
        weapons.push(weapon);
        console.log(`${this.name} grabs a ${weapon.name} from the cavern floor`);
    };
    this.toString = function(){
      if (weapons.length > 0)
        return `${this.name} looks angry and wields a ${weapons.find(w => w.equipped).name}`;
      else
        return `${this.name} looks peaceful`;
    }
}

// the prototype:
PrivateBarbarian.prototype = {
    constructor: PrivateBarbarian,
    talks: function(){
        console.log("I am " + this.name + " !!!");
    },
    saysHi: function (){
        console.log("Hi! I am " + this.name);
    }
};

In the example above we’ve done the following changes:

  • We have modified the constructor function so that the weapons variable is not a property of the object that would be created with the function but a simple variable inside the function itself.
  • We have moved the equipsWeapon function from the prototype to the constructor function and updated its body so that it encloses the weapons variable.

As a result, if we create a new PrivateBarbarian instance, it will not expose any weapons property to the outside world but this variable will still exist and act as if it was a private member of the object:

1
2
3
4
5
6
7
8
9
// we cannot access the weapons of the barbarian because
// they are not part of the object
var privateBarbarian = new PrivateBarbarian('krox');
console.log(`private barbarian weapons: ${privateBarbarian.weapons}`);
// => private barbarian weapons: undefined
privateBarbarian.equipsWeapon({name:'Two-handed Hammer'});
// => krox grabs a Two-handed Hammer from the cavern floor
console.log(privateBarbarian.toString());
// => krox looks angry and wields a Two-handed Hammer

In the same way that you have private variables you can have private methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function PrivateBarbarian(name){
    // unchanged code from previous example

    this.toString = function(){
      if (weapons.length > 0) return formatWeaponizedBarbarian();
      else return formatPeacefulBarbarian();
    }

    function formatWeaponizedBarbarian(){
      return `${name} looks angry and wields
 a ${weapons.find(w => w.equipped).name}`;
    }

    function formatPeacefulBarbarian(){
      return `${name} looks peaceful`;
    }
}

In summary, if you want to use closures to manage data privacy with classes you are going to need to define your methods inside the constructor function and not the prototype. This means that each single instance will have its own method property, and therefore they won’t be shared by all instances via the prototype. As a result this information hiding strategy will make you incur in a bigger memory footprint.

Classes and Information Hiding With ES6 Symbols

Using ES6 symbols allows you to achieve data privacy and keep your methods in the prototype. The trick is to keep your symbols private as well.

In order to do that we are going to define a very simple module. Modules in JavaScript let you wrap pieces of related functionality. We can create a simple characters module to store our characters using this pattern:

1
2
3
4
5
6
7
8
// a simple module 
(function(characters){
    characters.SymbolicBarbarian = SymbolicBarbarian;
    // etc...
}(window.characters = window.characters || {}))

// outside world only has access to whatever we expose
// via the characters object

Where we use a function – a new variable scope – to represent the module itself. We pass a characters object to the module function that will augment it with functionality that can later be used by the rest of the application.

With the creation of this module we will achieve two things: we are going to have a place where to keep our symbols (the function scope) and we are going to expose a new SymbolicBarbarian class that is going to use these symbols to obtain data privacy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(function(characters){
  characters.SymbolicBarbarian = SymbolicBarbarian;

  let weapons = Symbol('weapons');

  // the constructor function:
  function SymbolicBarbarian(name){
      this.name = name;
      this["character class"] = "barbarian";
      this.hp = 200;
      this[weapons] = [];
  }

  // the prototype:
  SymbolicBarbarian.prototype = {
      constructor: SymbolicBarbarian,
      talks: function(){
          console.log("I am " + this.name + " !!!");
      },
      equipsWeapon: function(weapon){
          weapon.equipped = true;
          this[weapons].push(weapon);
              console.log(`${this.name} grabs a ${weapon.name}
from the cavern floor`);
      },
      saysHi: function (){
          console.log("Hi! I am " + this.name);
      },
      toString: function(){
      if (this[weapons].length > 0)
        return `${this.name} looks angry and wields a
${this[weapons].find(w => w.equipped).name}`;
      else
        return `${this.name} looks peaceful`;
    }
};

}(window.characters = window.characters || {}))

Using the weapons symbol we can create a weapons property that can only be indexed if you have access to the symbol itself. Because the symbol is part of the characters module scope it’s only accessible to that function scope and therefore the SymbolicBarbarian class that also lives in that same scope. As a result the weapons property behaves like a private property of the SymbolicBarbarian class:

1
2
3
4
5
6
7
var symbolicBarbarian = new window.characters.SymbolicBarbarian('khaaarg');
symbolicBarbarian.equipsWeapon({name: 'katana sword'});
// => khaaarg grabs a katana sword from the cavern floor
console.log(`khaaarg weapons: ${symbolicBarbarian.weapons}`);
// => khaaarg weapons: undefined
console.log(symbolicBarbarian.toString());
// => khaaarg looks angry and wields a katana sword

Closures vs Symbols with Classes

Closures ES6 Symbols
Closures let you achieve true privacy. You cannot achieve true privacy. A client can use the `getOwnPropertySymbols` method to get access to the symbols used within a class and therefore access to its private members.
Because you need to enclose variables with your methods, using closures forces you to move your methods from the prototype to the constructor function. This requires more memory since these methods are no longer shared by all instances. With symbols you can keep your methods in the prototype and therefore consume less memory.

Static Classes, Members and Methods

Static members and methods in C# are shared across all instances of a given class, can only access other static members and methods and are accessed by using the class name followed by the name of the member or method.

You can mimic static members and methods in JavaScript by augmenting the constructor functions with new properties. For instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// we extend the ClassyBarbarian constructor function from previous examples
// with two new properties
ClassyBarbarian.default = function(){
  return new Barbarian('default barbarian');
};

ClassyBarbarian.swordWieldingBarbarian = function(){
  var barbarian = new Barbarian('sword wielding barbarian');
  barbarian.equipsWeapon({name: 'sword'});
  return barbarian;
};

var defaultBarbarian = ClassyBarbarian.default();
console.log(defaultBarbarian.name);
// => default barbarian
var swordWieldingBarbarian = ClassyBarbarian.swordWieldingBarbarian();
console.log(swordWieldingBarbarian.name);
// => sword wielding barbarian

Because these are properties of the constructor function and not of any instance in particular, they’ll only be accessible by having a reference to the constructor function. Additionally these members of methods will only be able to access other static members and methods since they are not tied to any instance in particular.

A static class is that which has only static members and methods, is sealed and cannot be instantiated. In a similar way to what you’ve seen in these examples, you can define a static class as a constructor function that only has “static” members and methods – properties assigned to the constructor function itself. This is going to give you a similar feeling to using static classes in C# but it is a little bit of a stretch since you can still instantiate objects with that constructor function and have it be part of an inheritance chain. You can solve both these problems by throwing an error when the constructor function is called:

1
2
3
4
5
function DateHelpers(){ throw Error('static class'); };
DateHelpers.ToJavaScriptMonth = function(month){
    // JavaScript months are 0 based
    return month - 1;
}

This may be trying to bring C# into JavaScript too far and you might have a simpler solution by just using an object initalizer with methods and properties.

Method overloading

In Useful Function Patterns: Function Overloading we learned about several patterns to implement function overloading in JavaScript:

  • Argument inspection: Inspect how many arguments are passed to a function and which are their types then decide what to do.
  • Options object: Provide an options object that contains the arguments to the method. You get the benefits of named parameters and high extensibility.
  • ES6 defaults and destructuring: Defaults let you provide different signatures as default values will be used if some arguments are not passed into the function. Destructuring let’s you unwrap options objects in a very straightforward fashion.
  • Function Programming and Polymorphic Functions: You can define polymorphic functions by composing several functions that will be called in turn until you get a result from any of them.

You can use any of these with the classes that we have defined in this article so take a look back if you need to refresh them.

Mimicking Classical Inheritance in JavaScript

This is what you’ve learned to this point:

  • We can emulate a single C# class with a constructor function and prototype pair
  • The constructor function defines the properties of each instance and the prototype its methods
  • We can take advantage of the prototype chain to create an inheritance tree
  • There’s no access modifiers but you can get private members by using closures and ES6 symbols. Closures force you to move methods from the prototype to the constructor function before you can use them
  • You can add static members and methods to a class by augmenting its constructor function
  • You can use any function overloading technique that you’ve learned in these series to overload class methods.

But How do you go from a single class to a inheritance tree and emulate classical inheritance in JavaScript?

You can mimic classical inheritance as we know it in C# by following these two steps when creating your classes:

  1. Making sure that each constructor function calls its base type constructor function using call or apply. This will ensure that any instance of a class contains all properties defined in each and every base class (as they are defined in each constructor function)
  2. Use prototypical inheritance to ensure that any instance of a class inherits methods from every base class (as they are contained within each prototype)

Let’s see how to achieve classical inheritance with an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Inheritance Hierarchy:
//   DrawableGameObject -> MobileGameObject

function Position(x,y){
    this.x = x;
    this.y = y;
}
Position.prototype.toString = function(){
    return "[" + this.x + "," + this.y + "]";
}

function MobileGameObject(position){
    this.position = position;
}
MobileGameObject.prototype.movesTo = function(newPosition){
    console.log(`${this} moves to ${this.position}`);
    this.position = newPosition;
}

function DrawableGameObject(position, sprite){
    // call base type constructor function
    MobileGameObject.apply(this, position);
    this.sprite = sprite;
}
// establish prototypical inheritance 
// between DrawableGameObject and MobileGameObject
DrawableGameObject.prototype = Object.create(MobileGameObject.prototype);
DrawableGameObject.prototype.constructor = DrawableGameObject;
DrawableGameObject.prototype.draw = function(){
    console.log("drawing sprite: " + this.sprite);
    // draw sprite
};

In this example you have two classes, a MobileGameObject that represents some type of object that can move in a two-dimensional space and a DrawableGameObject which is some object that can be drawn in a screen via a sprite (an image). You can verify how:

  1. The DrawableGameObject constructor function calls the parent class constructor via MobileGameObject.apply(this, position);. This will ensure that a drawable game object will have both a sprite and position properties.
  2. The DrawableGameObject.prototype object is a new object that in turn has a MobileGameObject.prototype as prototype. This will ensure that a drawable game object will have access to both DrawableGameObject and MobileGameObject prototype methods.

We can extend our inheritance tree with yet another class, the Shaman:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Inheritance Hierarchy:
//   Shaman -> DrawableGameObject -> MobileGameObject

function Shaman(name, position, sprite){
    // call base type constructor function
    DrawableGameObject.call(this, position, sprite);
    this.name = name;
}
// establish prototypical inheritance 
// between Barbarian and DrawableGameObject
Shaman.prototype = Object.create(DrawableGameObject.prototype);
Shaman.prototype.constructor = Shaman;
Shaman.prototype.toString = function(){
    return this.name;
};
Shaman.prototype.heals = function(target){
    console.log(`${this} heals ${target} (+ 50hp)`);
    target.hp += 50;
}

And we can verify that indeed it works as expected by creating an instance of the Shaman class:

1
2
3
4
5
6
7
var koloss = new Shaman("Koloss", new Position(0,0), "koloss.jpg");
koloss.movesTo(new Position(5,5))
// => Koloss moves to [5,5]
koloss.draw()
// => drawing sprite: koloss.jpg
koloss.heals(conan);
// => Koloss heals Conan, the Barbarian

Method overriding

You can follow a similar pattern to the one you used in the constructor function to override or extend any method defined in a base class.

Let’s override the heals method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// constructor function
function WhiteShaman(name, position, sprite){
    // call base type constructor function
    Shaman.call(this, name, position, sprite);
}

// prototype
WhiteShaman.prototype = Object.create(Shaman.prototype);
WhiteShaman.prototype.constructor = WhiteShaman;
WhiteShaman.prototype.castsSlowCurse = function(target){
  console.log(`${this} casts slow on ${target}. ${target} seems to move slower`);
  if (target.curses) target.curses.push('slow');
  else target.curses = ['slow'];
};
WhiteShaman.prototype.heals = function(target){
    Shaman.prototype.heals.call(this, target);
    console.log(`${this} cleanses all negatives effects in ${target}`);
    target.curses = [];
    target.poisons = [];
    target.diseases = [];
}

In this case, the heals method overrides the Shaman.prototype.heals method and extends it with new functionality to remove curses, poisons and diseases:

1
2
3
4
5
var khaaar = new WhiteShaman('Khaaar', new Position(0,0), "khaaar.png");
khaaar.castsSlowCurse(conan);
// => Khaaar casts slow on Conan, the Barbarian. Conan, the Barbarian seems to move slower
khaaar.heals(conan);
// => Khaaar cleanses all negatives effects in Conan, the Barbarian

Notice how you are not forced to extend a method. You can also overwrite it completely by merely shadowing it since the JavaScript runtime will not call a method in a prototype if it exists in the current object:

1
2
3
4
5
6
// you don't need to overwrite and extend a method
// you can completely replace it
// the JavaScript runtime will make sure to call the right method:
WhiteShaman.prototype.toString = function(target){
    return `${this.name} the Shaman`;
}

Indeed you can appreciate how the toString method no longer returns Khaaar but Khaaar the Shaman:

1
2
khaaar.castsSlowCurse(conan);
// => Khaaar the Shaman casts slow on Conan, the Barbarian. Conan, the Barbarian seems to move slower

Simplifying Classical Inheritance in ES5

Now that you’ve arrived at the end of this article you may be thinking that writing classes in JavaScript is a ton of work. And you are completely right. That’s why it’s helpful to write a helper to make things easier for you and remove the boilerplate code.

Ideally we would define a function that would let us create a class by providing:

  • a constructor function
  • a prototype
  • optionally, a class to extend or derive from
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function newClass({constructor, methods:prototype, extends:BaseClass=Object}){

  // helper function that creates a new constructor function
  // that calls the base class constructor function
  function extendConstructor(ctor, ctorToExtend){
     return function(...args){
        ctorToExtend.apply(this, args)
        ctor.apply(this, args);
        return this;
     };
  }

  // make sure constructor calls base class constructor
  let extendingConstructor = extendConstructor(constructor, BaseClass);

  // set the class prototype to an object that has
  // the base class prototype as prototype
  extendingConstructor.prototype = Object.create(BaseClass.prototype);
  extendingConstructor.prototype.constructor = extendingConstructor;
  // extend the prototype with the *class* methods
  Object.assign(extendingConstructor.prototype, prototype);

  return extendingConstructor;
}

The newClass function takes the three ingredients for a class: constructor, prototype and base class and assembles them all together for you. It will make sure to create a new constructor function that calls the base constructor before your own class constructor. It will also create an appropriate prototype by creating an object with the base class prototype and combining it with your own class prototype. If you don’t provide a base class to extend it will use the Object class as default.

Let’s see the newClass function in action and define a new Berserker class:

1
2
3
4
5
6
7
8
9
10
11
12
var Berserker = newClass({
  constructor: function(name, position, sprite, animalSpirit){
    this.animalSpirit;
  },
  methods: {
    rageAttack: function(target){
      console.log(`${this} screams and hits ${target} with a terrible blow`);
      target.hp -= 100;
    }
  },
  extends: ClassyBarbarian
});

Much better right? Now you can start using your Berserker class to fill your army ranks with bold (and crazy) warriors:

1
2
3
4
5
var dwarfBerserker = new Berserker('Gloin', new Position(0,0), 'gloin.png', 'badger');
dwarfBerserker.rageAttack("conan");
// => Gloin screams and hits conan with a terrible blow
dwarfBerserker.equipsWeapon({name: 'Double bearded Axe'});
// => Gloin grabs a Double bearded Axe from the cavern floor

Concluding

In this article you learned how to mimic C# classes and classical inheritance in JavaScript.

You saw how combining a constructor function and a prototype object let’s you create something comparable to a class, where the constructor function defines your class members and the prototype defines your class methods. You then learned about data privacy with closures and symbols and how to write static members, methods and classes in JavaScript.

We continued extending the scope from a single class to multiple classes and you discovered how to achieve an equivalent experience to classical inheritance in C#. We could achieve this by calling a base class constructor function for a derived class constructor and by establishing a prototypical chain between each class prototype. We wrapped the article discussing how to override and extend class methods, and how to simplify class inheritance in JavaScript using a helper.

If you reflect a little bit about what you’ve learned in this article you’ll probably come to wonder: Really? Does writing a class in JavaScript need to be this hard?. Well that’s exactly what ES6 classes are trying to remedy by providing a much more familiar, simple and nicer syntax to writing classes in JavaScript. And that’s what you’ll learn in the next article.

Interested In Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

More Articles in These Series


  1. Remember that you can get access to all symbols used within an object via getOwnPropertySymbols and therefore symbols don’t offer true privacy like closures do.

Comments