JavaScript-mancy: Object Oriented Programming Preview - Introduction

Introduction

to the Path of Summoning and Commanding Objects (a.k.a. Object Oriented Programming)

Many ways to build a Golem there are,

cast its blueprint in clay
then recite the instantiation chants,

or put together the parts
that'll made the whole alive,

or bring it forth at once
with no prior thought required.

Many ways to build a Golem there are,
in JavaScript.

KeDo,
Master Artificer, JavaScript-mancy poems

/*

Mooleen sits in a dark corner of a tavern sipping a jug of the local brew.
She flinches. The local brew must surely have fire wyvern blood in it.
She silently observes the villagers around her.
They seem unhappy and nervous, as if they were expecting something terrible
was about to befall them at any second.

*/

mooleen.says("A month has passed since we dispatched Great");
mooleen.says("You would think they would be happier");

rat.says("Well, people don't like change or surprises");
rat.says("They're expecting that someone worse will take control");
rat.says("Better the devil you know...");

/*
A maid stops by Mooleen's table confused
*/
maid.says("Are you feeling ok sir? Speaking to yourself?");

rat.moves("out of the shadows");
maid.shrikes();

villager.shouts("A demon!!!");

rat.says("Great");
mooleen.says("That's just plain mean");

/*
The villagers quickly surround the dark corner with clubs, bottles and
whichever crude weapon they can muster.
*/
villager.shouts("Kill the demon!!");

mooleen.weaves("teleport('Caves of Infinity')");

/* 
Mooleen and rat blink out of existence just as various pointy weapons
blink into existence precisely where they were sitting a second
earlier.
*/

randalf.says("There you are!");
mooleen.says("here I am!");
rat.says("A demon!?");

randalf.exclaims("A demon? Where!!");
bandalf.says("Yes where!")
zandalf.looksWorriedAllAround();

mooleen.says("There's no demon");
randalf.asks("Are you sure?");

randalf.says("We need to be on our toes");
mooleen.asks("You too?");

randalf.says("Yes, it's been a month, they must be about to attack");
mooleen.says("They? Who!");

randalf.says("Could be anyone really... the dark brootherhood, the clan " + 
 "the silver guild, the red hand... They'll want to control Asturi");
randalf.says("You need to summon an army");

mooleen.says("An army?");
randalf.says("An army indeed, and bigger than the one before...");

mooleen.says("Really? Cause that took a looooong time to summon");
randalf.says("Well, That's because you're a novice");

mooleen.says("That's encouraging");
randalf.says("Oh, don't you worry, we'll take care of your ignorance");
mooleen.says("Ouch");

randalf.says("Let me tell you about OOP in JavaScript");

Let me Tell You About OOP in JavaScript

Welcome to the Path of Summoning 1 and Commanding Objects! In this part of this ancient manuscript you’ll learn how you can work with objects in JavaScript, how to define them, create them and even how to interweave them. By the end of it you’ll have mastered Object Oriented Programming in JavaScript and you’ll be ready to command your vast armies of objects into glory.

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 part of the series 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 2 of object-oriented programming in JavaScript. If you feel like you can’t follow the examples don’t worry. For in the upcoming chapters we will dive deeper into each of the concepts and techniques used, and we will discuss them separately and at a much slower pace.

C# Classes in JavaScript

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

// 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:

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. This method will then 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:

// 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:

// 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:

// 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:

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 and developer friendly syntax:

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

You also get access to the extend and super keywords, where extend lets you establish class inheritance and super lets you access methods from parent classes:

// 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);
  }
}

Again, we can verify that it works just like it did before by instantiating a classy wizard:

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. Let’s now look beyond.

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 them in the first place. You were meant to embrace prototypical inheritance, 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 those occassions when we need to use more gentle/shrew hand against our enemies. A ClassyThief class could look something like this:

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:

// class Bard
// should be able to:
// - cast powerful spells
// - steals many items
// - play beautiful music

Well, we’ve put ourselves 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 3)
  • 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 these behaviors and encapsulated them inside separate objects (canCastSpells, canSteal and canPlayMusic):

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:

// 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,
               canSteal);
  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 and static 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:

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.

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

Do you 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:

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:

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

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. In thischapter, 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, you’ll learn the basics of JavaScript’s 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. 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: Safer Object Composition with Traits ** you’ll learn about an object composition alternative to mixins called traits. Traits are as reusable and composable as mixins but are even more flexible and safe as they let you define required properties and resolve conflicts.

In *Black Tower Summoning Enhanced: Next Level Object Composition 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, ES6 proxies 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 next chapters 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!

randalf.says("See? There's a lot of stuff for you to learn");
mooleen.says("Is any of that going to help me get home?");

randalf.says("Most definitely.");
randalf.says("I have scourged our library and found nothing " + 
    "about this 'earth' you speak of");
randalf.says("The only other option is the golden library of Orrile...");

mooleen.says("Awesome! Then just show me the way");

randalf.says("... in Tates, guarded by The Deadly Seven... ");
mooleen.says("I can take care of them");

randalf.says("... and the vast host of armies " + 
             "of the most powerful sorcerer alive");
mooleen.says("I see");
rat.says("downer");

mooleen.says("You were saying something about OOP techniques?...");
  1. In Fantasy, Wizards of all sorts and kinds summon or call forth creatures to act as servants, or warriors, and follow the wizard’s commands. As a JavaScript-mancer you’ll be able to use Object Oriented Programming to summon your own objects into reality and do with them as you please. 

  2. In this section 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 chapters about OOP. 

  3. 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 them you are violating this principle. That’s because the derived class breaks the expectations established by the base class (i.e. that you should be able to use both methods).