An Introduction to Object Oriented Programming in JavaScript for C# Developers

| 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.

JavaScript OOP story is pretty special. When I started working seriously with JavaScript some years ago, one of my first concerns as a C# developer coming to JavaScript was to find out how to write a class. I had a lot of prowess in C# and I wanted to bring all that knowledge and ability into the world of JavaScript, so my first approach was to try to map every C# concept into JavaScript. I saw classes, which are such a core construct in C# and which were such an important part of my programming style at the time, as my secret passage to being proficient in JavaScript.

Well, it took me a long while to understand how to mimic classical inheritance in JavaScript but it was time well spent because along the way I learnt a lot about JavaScript and about the many different ways in which it supports object-oriented programming. This quest helped me look beyond classical inheritance into other OOP styles more adequate to JavaScript where flexibility and expressiveness reign supreme over the strict and fixed taxonomies of classes.

In this series of articles about JavaScript OOP I will attempt to bring you with me through the same journey that I experienced. We will start with how to achieve classical inheritance in JavaScript, so you can get a basic level of proficiency by translating your C# skills into JavaScript and then we will move beyond that into new patterns that truly leverage JavaScript as a language and which will blow your mind.

Let’s get a taste of what is in store for you by getting a high level overview of object-oriented programming in JavaScript. If you feel like you can’t follow the examples don’t worry. For in the upcoming articles we will dive deeper in each of the concepts and constructs used, and we will discuss them separately and at a much slower pace.

Note that in this article I am going to make a lot of generalizations and simplifications in order to give a simple and clear introduction to OOP in JavaScript. I’ll dive into each concept in greater detail and with an appropriate level of correctness in the rest of the articles about OOP.

C# Classes in JavaScript

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

A C# class is more or less equivalent to a JavaScript constructor function and prototype pair:

1
2
3
4
5
6
7
8
9
10
11
12
// Here we have a Minion constructor function
function Minion(name, hp){
  // the constructor function usually defines the data within a "class"
  // the properties contained within a constructor function will be part
  // of each object created with this function
  this.name = name;
  this.hp = hp;
}

// the prototype usually defines the methods within a "class"
// It is shared across all Minion instances
Minion.prototype.toString = function() {return this.name;};

The constructor function represents how an object should be constructed (created) while the prototype represents a piece of reusable behavior. In practice, the constructor function usually defines the data members within a “class” while the prototype defines its methods.

You can instantiate a new Minion object by using the new operator on the constructor function:

1
2
3
4
5
6
7
8
9
10
11
12
13
var orc = new Minion('orc', 100);
console.log(orc);
// => [object Object] {
//  hp: 100,
//  name: "orc",
//  toString: function () {return this.name;}
//}

console.log(orc.toString())
// => orc

console.log('orc is a Minion: ' + (orc instanceof Minion));
// => true

As a result of instantiating an orc we get a new Minion object with two properties hp and name. The Minion object also has a hidden property called [[prototype]] that points to its prototype which is an object that has a method toString. This prototype and its toString method are shared across all instances of the Minion class.

When you call orc.toString the JavaScript runtime checks whether or not the orc object has a toString method and if it can’t find it, like in this case, it goes down the prototype chain until it does. The prototype chain is established by the object itself, its prototype, its prototype’s prototype and so on. In this case, the prototype chain leads to the Minion.prototype object that has a toString method that would be called and evaluated as this.name (orc).

We can mimic classical inheritance by defining a new “class” Wizard and make it inherit from Minion:

1
2
3
4
5
6
7
8
9
10
11
12
// Behold! A Wizard!
function Wizard(element, mana, name, hp){
  // the constructor function calls its parent constructor function
  // using [Function.prototype.call] (or apply)
  Minion.call(this, name, hp);
  this.element = element;
  this.mana = mana;
}

// the prototype of the Wizard is a Minion object
Wizard.prototype = Object.create(Minion.prototype);
Wizard.prototype.constructor = Wizard;

We achieve classical inheritance by:

  1. calling the Minion constructor function from the Wizard constructor
  2. assigning a Minion object (created via Object.create) as prototype of the Wizard “class”

With the constructor delegation we ensure that a Wizard object has all the properties of a Minion object. While with the prototype chain we ensure that all the methods in the Minion prototype are available to a Wizard object.

We can also augment the Wizard prototype with new methods:

1
2
3
4
5
6
// we can augment the prototype with a new method to cast mighty spells
Wizard.prototype.castsSpell = function(spell, target){
    console.log(this + ' casts ' + spell + ' on ' + target);
    this.mana -= spell.mana;
    spell(target);
};

Or even override or extend existing methods within its base “class” Minion:

1
2
3
4
// we can also override and extend methods
Wizard.prototype.toString = function(){
    return Minion.prototype.toString.apply(this, arguments) + ", the " + this.element +" Wizard";
};

Finally we can verify that everything works as expected by instantiating our very own powerful wizard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var gandalf = new Wizard("Grey", /* mana */ 50, "Gandalf", /* hp */ 50);
console.log('Gandalf is a Wizard: ' + (gandalf instanceof Wizard));
// => Gandalf is a Wizard: true
console.log('Gandalf is a Minion: ' + (gandalf instanceof Minion));
// => Gandalf is a Minion: true

console.log(gandalf.toString());
// => Gandalf, the Grey Wizard

var lightningSpell = function(target){
  console.log('A bolt of lightning electrifies ' + target + '(-10hp)');
  target.hp -= 10;
};
lightningSpell.mana = 5;
lightningSpell.toString = function(){ return 'lightning spell';};
gandalf.castsSpell(lightningSpell, orc);
// => Gandalf, the Grey Wizard casts lightning spell on orc
// => A bolt of lightning electrifies orc (-10hp)

As you can see from these previous examples writing “classes” prior to ES6 was no easy feat, it required a lot of moving components and a lot of code. That’s why ES6 brings classes along which provide a much nicer syntax to what you’ve seen thus far. This means that instead of having to handle constructor functions and prototypes yourself, you get the new class keyword that nicely wraps both into a more coherent syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// this is the equivalent of the Minion
class ClassyMinion{
  constructor(name, hp){
    this.name = name;
    this.hp = hp;
  }
  toString(){
    return this.name;
  }
}

let classyOrc = new ClassyMinion('classy orc', 50);
console.log(classyOrc);
// => [object Object] {
//  hp: 100,
//  name: "classy orc"
//}

console.log(classyOrc.toString());
// => classy orc

console.log('classy orc is a ClassyMinion: ' + (classyOrc instanceof ClassyMinion));
// => classy orc is a ClassyMinion: true

And the extend and super keywords:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// and this is the equivalent of the Wizard
class ClassyWizard extends ClassyMinion{
  constructor(element, mana, name, hp){
    // super lets you access the parent class methods
    super(name, hp);
    this.element = element;
    this.mana = mana;
  }
  toString(){
    return super.toString() + ", the " + this.element +" Wizard";
  }
  castsSpell(spell, target){
    console.log(this + ' casts ' + spell + ' on ' + target);
    this.mana -= spell.mana;
    spell(target);
  }
}

Where extend lets you establish class inheritance and super lets you access methods from parent classes. Again, we can verify that it works just like it did before by instantiating a classy wizard:

1
2
3
4
5
6
7
8
9
10
11
12
let classyGandalf = new Wizard("Grey", /* mana */ 50, "Classy Gandalf", /* hp */ 50);
console.log('Classy Gandalf is a ClassyWizard: ' + (classyGandalf instanceof ClassyWizard));
// => Classy Gandalf is a ClassyWizard: true
console.log('Classy Gandalf is a ClassyMinion: ' + (classyGandalf instanceof ClassyMinion));
// => Classy Gandalf is a ClassyMinion: true

console.log(classyGandalf.toString());
// => Classy Gandalf, the Grey Wizard

classyGandalf.castsSpell(lightningSpell, classyOrc);
// => Classy Gandalf, the Grey Wizard casts lightning spell on classy orc
// => A bolt of lightning electrifies classy orc(-10hp)

It is important to highlight though that ES6 classes are just syntactic sugar. Under the hood, these ES6 classes that you have just seen are translated into constructor function/prototype pairs.

And that is how you mimic classical inheritance in JavaScript. But let’s look beyond it.

OOP Beyond Classes

There are a lot of people in the JavaScript community that claim that the cause of JavaScript not having a nice way to mimic classical inheritance, not having classes, is that you were not meant to use it in the first place. You were meant to embrace prototypical inheritance which is the natural way of working with inheritance in JavaScript, instead of hacking it to make it behave sort of like classical inheritance.

In the world of prototypical inheritance you only have objects, and particularly objects that are based upon other objects which we call prototypes. Prototypes lend behaviors to other objects by means of delegation (via the prototype chain) or by the so called concatenative inheritance which consists in copying behaviors.

Let’s illustrate the usefulness of this type of inheritance with an example. Imagine that, in addition to wizards, we also need to have some thieves for when we need to use more gentle/shrew hand against our enemies. A ClassyThief class could look something like this:

1
2
3
4
5
6
7
8
9
10
11
class ClassyThief extends ClassyMinion{
  constructor(name, hp){
    super(name, hp);
  }
  toString(){
    return super.toString() + ", the Thief";
  }
  steals(target, item){
    console.log(`${this} steals ${item} from ${target}`);
  }
}

And let’s say that a couple of weeks from now, we realize that it would be nice to have yet another type of minion, one that can both cast spells and steals, and why not? Play some music. Something like a Bard. In pseudo-code we would describe it as follows:

1
2
3
4
5
// class Bard
// should be able to:
// - cast powerful spells
// - steals many items
// - play beautiful music

Well we are in a pickle here. Classical inheritance tends to build rigid taxonomies of types where something is a Wizard, something is a Thief but it cannot be both. How would we solve the issue of the Bard using classical inheritance in C#? Well…

  • We could move both castsSpell and steals methods to a base class SpellCastingAndStealingMinion that all three types could inherit. The ClassyThief would throw an exception when casting spell and so would the ClassyWizard when stealing. Not a very good solution (goodbye Liskov principle 1)
  • We could create a SpellCastingAndStealingMinion that duplicates the functionality in ClassyThief and ClassyWizard and make the Bard inherit from it. This solution would imply code duplication and thus additional maintenance.
  • We could define interfaces for these behaviors ICanSteal, ICanCastSpells and make each class implement these interfaces. Nicer but we would need to provide an specific implementation in each separate class. No so much code reuse here.

So none of these solutions are very attractive, they involve bad design, code duplication or both. Can JavaScript helps us to achieve a better solution to this problem? Yes it can

Imagine that we broke down all behaviors and encapsulated them inside separate objects (canCastSpells, canSteal and canPlayMusic):

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
let canCastSpells = {
  castsSpell(spell, target){
    console.log(this + ' casts ' + spell + ' on ' + target);
    this.mana -= spell.mana;
    spell(target);
  }
};

let canSteal = {
  steals(target, item){
    console.log(`${this} steals ${item} from ${target}`);
  }
};

let canPlayMusic = {
  playsMusic(){
    console.log(`${this} grabs his ${this.instrument} and starts playing music`);
  }
};

let canBeIdentifiedByName = {
  toString(){
    return this.name;
  }
};

Now that we have encapsulated each behavior in a separate object we can compose them together to provide the necessary functionality to a wizard, a thief and a bard:

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
// and now we can create our objects by composing this behaviors together
function TheWizard(element, mana, name, hp){
  let wizard = {element,
                mana,
                name,
                hp};
  Object.assign(wizard,
               canBeIdentifiedByName,
               canCastSpells);
  return wizard;
}

function TheThief(name, hp){
  let thief = {name,
               hp};
  Object.assign(thief,
               canBeIdentifiedByName,
               canSteal);
  return thief;
}

function TheBard(instrument, mana, name, hp){
  let bard = {instrument,
                mana,
                name,
                hp};
  Object.assign(bard,
               canBeIdentifiedByName,
               canSteal,
               canCastSpells,
               canPlayMusic);
  return bard;
}

And in a very expressive way we can see how a wizard is someone than can cast spells, a thief is someone that can steal and a bard someone that not only can cast spells and steal but can also play music. By stepping out of the rigid limits of classical inheritance and strong typing we get to a place where we can easily reuse behaviors and compose new objects in a very flexible and extensible manner.

We can verify that indeed this approach works beautifully:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let wizard = TheWizard('fire', 100, 'Randalf, the Red', 10);
wizard.castsSpell(lightningSpell, orc);
// => Randalf, the Red casts lightning spell on orc
// => A bolt of lightning electrifies orc(-10hp)

let thief = TheThief('Locke Lamora', 100);
thief.steals('orc', /*item*/ 'gold coin');
// => Locke Lamora steals gold coin from orc

let bard = TheBard('lute', 100, 'Kvothe', 100);
bard.playsMusic();
// => Kvothe grabs his lute and starts playing music
bard.steals('orc', /*item*/ 'sandwich');
// => Kvothe steals sandwich from orc
bard.castsSpell(lightningSpell, orc);
// => Kvothe casts lightning spell on orc
// =>A bolt of lightning electrifies orc(-10hp)

The Object.assign in the examples is an ES6 method that lets you extend an object with other objects. This is effectively the concatenative prototypical inheritance we mentioned previously.

We usually call these objects mixins. A mixin in JavaScript is just an object that you compose with other objects to provide them with additional behavior or state. In the simplest example of mixins you just have a single object extending another object, but there’s also functional mixins, where you use functions instead. We will cover all these mixin patterns in detail later in these series.

This object composition technique constitutes a very interesting and flexible approach to object-oriented programming that isn’t available in C#. But in JavaScript we can use it even with ES6 classes!

Combining Classes with Object Composition

Remember that ES6 classes are just syntactic sugar over the existing prototypical inheritance model. They may look like classical inheritance but they are not. This means that the following mix of ES6 classes and object composition would work:

1
2
3
4
5
6
7
8
9
10
11
12
class ClassyBard extends ClassyMinion{
  constructor(instrument, mana, name, hp){
    super(name, hp);
    this.instrument = instrument;
    this.mana = mana;
  }
}

Object.assign(ClassyBard.prototype,
       canSteal,
       canCastSpells,
       canPlayMusic);

In this example we extend the ClassyBard prototype with new functionality that will be shared by all future instances of ClassyBard. If we instantiate a new bard we can verify that it can steal, cast spells and play music.

1
2
3
4
5
6
7
8
let anotherBard = new ClassyBard('guitar', 100, 'Jimi Hendrix', 100);
anotherBard.playsMusic();
// => Kvothe grabs his lute and starts playing music
anotherBard.steals('orc', /*item*/ 'silver coin');
// => Kvothe steals silver coin from orc
anotherBard.castsSpell(lightningSpell, orc);
// => Kvothe casts lightning spell on orc
// =>A bolt of lightning electrifies orc(-10hp)

This is an example of delegation-based prototypical inheritance in which methods such as steals, castsSpell and playsMusic are delegated to a single prototype object (instead of being appended to each object).

So far you’ve seen classical inheritance mimicked in JavaScript, ES6 classes and object composition via mixin objects, but there’s much more to learn and in greater detail! Take a sneak peak at what you’ll learn in each of the upcoming articles:

The Path of the Object Summoner Step by Step

In Summoning Fundamentals: an Introduction to Object Oriented Programming in JavaScript you’ll start by understanding the basic constructs needed to define and instantiate objects in JavaScript where constructor functions and the new operator will join what you’ve discovered thus far about object initializers. You’ll review how to achieve information hiding and you’ll learn the basics of JavaScript prototypical inheritance model and how you can use it to reuse code/behaviors and improve your memory footprint. You’ll complete the foundations of JavaScript OOP by understanding how JavaScript achieves polymorphism.

In White Tower Summoning or Emulating Classical Inheritance in JavaScript you’ll use constructor functions in conjunction with prototypes to create the equivalent of C# classes in JavaScript. You’ll then push the boundaries of JavaScript inheritance model further and emulate C# classical inheritance building inheritance chains with method extension and overriding just like in C#.

In White Tower Summoning Enhanced: the Marvels of ES6 Classes you’ll learn about the new ES6 Class syntax and how it provides a much better class development experience over what it was possible prior to ES6.

In Black Tower Summoning: Objects Interweaving Objects with Mixins we’ll go beyond classical inheritance into the arcane realm of object composition with mixins. You’ll learn about the extreme extensibility of object-oriented programming based on object composition and how you can define small pieces of reusable behavior and properties and combine them together to create powerful objects (effectively achieving multiple inheritance).

In Black Tower Summoning Enhanced: Interweaving Objects with Stamps you’ll find out about a new way to work with objects in JavaScript called Stamps that brings object composability to the next level.

Finally, you’ll dive into the depths of Object Internals and discover the mysteries of the low level JavaScript Object APIs and the new ES6 Reflection APIs.

Concluding

JavaScript is a very versatile language that supports a lot of programming paradigms and different styles of Object-Oriented Programming. In the upcoming articles you’ll see how you can combine a small number of primitive constructs and techniques to achieve a variety of OOP styles.

JavaScript, like in any other part of the language, gives you a lot of freedom when working with objects, and sometimes you’ll feel like there are so many options and things you can do that you won’t know what’s the right path. Because of that, I’ll try to provide you with as much guidance as I can and highlight the strengths and weaknesses of each of the options available.

Get ready to learn some JavaScript OOP!

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

Have a awesome week!!!

More Articles in These Series


  1. The Liskov substitution principle is one of the S.O.L.I.D. principles of object-oriented design. It states that derived classes must be substitutable for their base classes. This means that a derived class should behave as portrayed by its base class and not break the expectations created by its interface. In this particular example if you have a castsSpell and a steals method in the base class, and a derived class throws an exception when you call the steals method you are violating this principle because the derived class breaks the expectations established by the base class (i.e. that you should be able to use both methods).

Comments