Introduction to the Path of Summoning and Commanding Objects (a.k.a. Object Oriented Programming)
A Soft Introduction to OOP in JavaScript
Welcome to the Path of Summoning 1 and Commanding Objects! In this part of these 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 this part you’ll have mastered Object Oriented Programming in JavaScript and you’ll be ready to command your vast armies of objects into victory.
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 book 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 in each of the concepts and constructs 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:
1 // Here we have a Minion constructor function
2 function Minion(name, hp){
3 // the constructor function usually defines the data within
4 // a "class", the properties contained within a constructor
5 // function will be part of each object created
6 // with this function
7 this.name = name;
8 this.hp = hp;
9 }
10
11 // the prototype usually defines the methods within a "class"
12 // It is shared across all Minion instances
13 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 var orc = new Minion('orc', 100);
2 console.log(orc);
3 // => [object Object] {
4 // hp: 100,
5 // name: "orc",
6 // toString: function () {return this.name;}
7 //}
8
9 console.log(orc.toString())
10 // => orc
11
12 console.log('orc is a Minion: ' + (orc instanceof Minion));
13 // => 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 // Behold! A Wizard!
2 function Wizard(element, mana, name, hp){
3 // the constructor function calls its parent constructor function
4 // using [Function.prototype.call] (or apply)
5 Minion.call(this, name, hp);
6 this.element = element;
7 this.mana = mana;
8 }
9
10 // the prototype of the Wizard is a Minion object
11 Wizard.prototype = Object.create(Minion.prototype);
12 Wizard.prototype.constructor = Wizard;
We achieve classical inheritance by:
- calling the
Minionconstructor function from theWizardconstructor - assigning a
Minionobject (created viaObject.create) as prototype of theWizard“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 // we can augment the prototype with a new method
2 // to cast mighty spells
3 Wizard.prototype.castsSpell = function(spell, target){
4 console.log(this + ' casts ' + spell + ' on ' + target);
5 this.mana -= spell.mana;
6 spell(target);
7 };
Or even override or extend existing methods within its base “class” Minion:
1 // we can also override and extend methods
2 Wizard.prototype.toString = function(){
3 return Minion.prototype.toString.apply(this, arguments) +
4 ", the " + this.element +" Wizard";
5 };
Finally we can verify that everything works as expected by instantiating our very own powerful wizard:
1 var gandalf = new Wizard("Grey", /* mana */ 50,
2 "Gandalf", /* hp */ 50);
3 console.log('Gandalf is a Wizard: ' + (gandalf instanceof Wizard));
4 // => Gandalf is a Wizard: true
5 console.log('Gandalf is a Minion: ' + (gandalf instanceof Minion));
6 // => Gandalf is a Minion: true
7
8 console.log(gandalf.toString());
9 // => Gandalf, the Grey Wizard
10
11 var lightningSpell = function(target){
12 console.log('A bolt of lightning electrifies ' +
13 target + '(-10hp)');
14 target.hp -= 10;
15 };
16 lightningSpell.mana = 5;
17 lightningSpell.toString = function(){ return 'lightning spell';};
18 gandalf.castsSpell(lightningSpell, orc);
19 // => Gandalf, the Grey Wizard casts lightning spell on orc
20 // => 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 // this is the equivalent of the Minion
2 class ClassyMinion{
3 constructor(name, hp){
4 this.name = name;
5 this.hp = hp;
6 }
7 toString(){
8 return this.name;
9 }
10 }
11
12 let classyOrc = new ClassyMinion('classy orc', 50);
13 console.log(classyOrc);
14 // => [object Object] {
15 // hp: 100,
16 // name: "classy orc"
17 //}
18
19 console.log(classyOrc.toString());
20 // => classy orc
21
22 console.log('classy orc is a ClassyMinion: ' +
23 (classyOrc instanceof ClassyMinion));
24 // => classy orc is a ClassyMinion: true
And the extend and super keywords:
1 // and this is the equivalent of the Wizard
2 class ClassyWizard extends ClassyMinion{
3 constructor(element, mana, name, hp){
4 // super lets you access the parent class methods
5 super(name, hp);
6 this.element = element;
7 this.mana = mana;
8 }
9 toString(){
10 return super.toString() + ", the " + this.element +" Wizard";
11 }
12 castsSpell(spell, target){
13 console.log(this + ' casts ' + spell + ' on ' + target);
14 this.mana -= spell.mana;
15 spell(target);
16 }
17 }
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 let classyGandalf = new Wizard("Grey", /* mana */ 50,
2 "Classy Gandalf", /* hp */ 50);
3 console.log('Classy Gandalf is a ClassyWizard: ' +
4 (classyGandalf instanceof ClassyWizard));
5 // => Classy Gandalf is a ClassyWizard: true
6 console.log('Classy Gandalf is a ClassyMinion: ' +
7 (classyGandalf instanceof ClassyMinion));
8 // => Classy Gandalf is a ClassyMinion: true
9
10 console.log(classyGandalf.toString());
11 // => Classy Gandalf, the Grey Wizard
12
13 classyGandalf.castsSpell(lightningSpell, classyOrc);
14 // => Classy Gandalf, the Grey Wizard casts lightning
15 // spell on classy orc
16 // => 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 class ClassyThief extends ClassyMinion{
2 constructor(name, hp){
3 super(name, hp);
4 }
5 toString(){
6 return super.toString() + ", the Thief";
7 }
8 steals(target, item){
9 console.log(`${this} steals ${item} from ${target}`);
10 }
11 }
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 // class Bard
2 // should be able to:
3 // - cast powerful spells
4 // - steals many items
5 // - 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
castsSpellandstealsmethods to a base classSpellCastingAndStealingMinionthat all three types could inherit. TheClassyThiefwould throw an exception when casting spell and so would theClassyWizardwhen stealing. Not a very good solution (goodbye Liskov principle 3) - We could create a
SpellCastingAndStealingMinionthat duplicates the functionality inClassyThiefandClassyWizardand make theBardinherit from it. This solution would imply code duplication and thus additional maintenance. - We could define interfaces for these behaviors
ICanSteal,ICanCastSpellsand 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 let canCastSpells = {
2 castsSpell(spell, target){
3 console.log(this + ' casts ' + spell + ' on ' + target);
4 this.mana -= spell.mana;
5 spell(target);
6 }
7 };
8
9 let canSteal = {
10 steals(target, item){
11 console.log(`${this} steals ${item} from ${target}`);
12 }
13 };
14
15 let canPlayMusic = {
16 playsMusic(){
17 console.log(`${this} grabs his ${this.instrument}` +
18 ` and starts playing music`);
19 }
20 };
21
22 let canBeIdentifiedByName = {
23 toString(){
24 return this.name;
25 }
26 };
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 // and now we can create our objects by composing
2 // this behaviors together
3 function TheWizard(element, mana, name, hp){
4 let wizard = {element,
5 mana,
6 name,
7 hp};
8 Object.assign(wizard,
9 canBeIdentifiedByName,
10 canCastSpells);
11 return wizard;
12 }
13
14 function TheThief(name, hp){
15 let thief = {name,
16 hp};
17 Object.assign(thief,
18 canBeIdentifiedByName,
19 canSteal);
20 return thief;
21 }
22
23 function TheBard(instrument, mana, name, hp){
24 let bard = {instrument,
25 mana,
26 name,
27 hp};
28 Object.assign(bard,
29 canBeIdentifiedByName,
30 canSteal,
31 canCastSpells,
32 canSteal);
33 return bard;
34 }
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 let wizard = TheWizard('fire', 100, 'Randalf, the Red', 10);
2 wizard.castsSpell(lightningSpell, orc);
3 // => Randalf, the Red casts lightning spell on orc
4 // => A bolt of lightning electrifies orc(-10hp)
5
6 let thief = TheThief('Locke Lamora', 100);
7 thief.steals('orc', /*item*/ 'gold coin');
8 // => Locke Lamora steals gold coin from orc
9
10 let bard = TheBard('lute', 100, 'Kvothe', 100);
11 bard.playsMusic();
12 // => Kvothe grabs his lute and starts playing music
13 bard.steals('orc', /*item*/ 'sandwich');
14 // => Kvothe steals sandwich from orc
15 bard.castsSpell(lightningSpell, orc);
16 // => Kvothe casts lightning spell on orc
17 // =>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
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 class ClassyBard extends ClassyMinion{
2 constructor(instrument, mana, name, hp){
3 super(name, hp);
4 this.instrument = instrument;
5 this.mana = mana;
6 }
7 }
8
9 Object.assign(ClassyBard.prototype,
10 canSteal,
11 canCastSpells,
12 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 let anotherBard = new ClassyBard('guitar', 100, 'Jimi Hendrix', 100);
2 anotherBard.playsMusic();
3 // => Kvothe grabs his lute and starts playing music
4 anotherBard.steals('orc', /*item*/ 'silver coin');
5 // => Kvothe steals silver coin from orc
6 anotherBard.castsSpell(lightningSpell, orc);
7 // => Kvothe casts lightning spell on orc
8 // =>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 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: 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 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!