To Class or Compose in Object-Oriented JavaScript — pt 1
Classical Subclassing
So you need a function to stamp out multiple instances of an object for your application — should you use classical* constructors or compose them from factory functions? JavaScript has powerful functional and object-oriented utilities making it easy to go either direction, but it’s worth understanding the differences between the two before deciding which is best for your use-case.
I’ll blast through some of the standard class patterns here in part one before we move on to the comparison in part two. Let’s start with the tried and true ES5 syntax for classes to get an understanding of JavaScript’s approach to class.
function Combatant (weapon, speed) {
this.speed = speed;
this.weapon = weapon;
}
Here’s our first class constructor, “Combatant.” It’s just a function that assigns some own properties of a class.
Combatant.prototype.fight = function(){
return "Wields " + this.weapon
}
Combatant.prototype.run = function(){
return this.speed * 2;
}
Then afterward, we define the methods with explicit assignment to the given “prototype” object. “prototype” property exists on every function-object incase it may be used as a constructor and provide prototypal inheritance. JavaScript makes no initial distinction for functions that will be used as constructors. They’re all just functions. Note that the entire process is not encapsulated in a single particular scope. Its a totally separate activity for every bit of class definition.
Here, the constructor observes it’s destiny and creates an instance:
var swordsman = new Combatant("sword", 5);
Now “swordsman” is an instance of “Combatant” with some own properties (swordsman.weapon, swordsman.speed) with methods prototypically inherited from “Combatant.prototype”. This is what JavaScript classes do; they provide prototypal inheritance. This is all the work of the additional operator: the “new” keyword.
“swordsman”, as an instance, doesn’t actually have those class methods, it delegates to them. Or, it refers to the object it — as they say — is an instance of. Nonetheless, the “swordsman” instance effectively possesses all the functionality defined by it’s constructor. Now “Combatant” is a class.
A quick refresher on all that occurs with the “new” operation:
- Creates a new object within the scope of the constructor function.
- That object is now the contextual reference for “this”, thus allowing for own property value assignments:
this.speed = speed;
this.weapon = weapon;
3. That same object is returned.
4. And finally, the object is given that famous JavaScript prototypal inheritance relationship with the constructor’s “prototype” property.
Many more instances of “Combatant” are now expected to populate an application. But what if we want a subclass? Or, an object similar to “Combatant” that could itself be a class and pump out it’s own instances. Enter, the Classical Subclass.
function Swordsman(speed, rangeBonus) {
Combatant.call(this, "sword", speed - rangeBonus)
}
See what’s happening there inside the “Swordsman” constructor? It’s simply calling the superclass (the class which the subclass will be based on) while applying “Swordsman’s” eventual functionally-scoped this to it. The implication is that Swordsman WILL be used with new to perform new’s unique object creation operation. Sound confusing? Because it is. We’re rewiring all these operations to makeshift a subclass. When new operates on “Swordsman”, the newly created this object will also run through the superclass function to do all the superclass’s own property assignments, as previously defined, to the this object (keep in mind, this can simply be seen as a variable for an object). What’s crazy is that the superclass function isn’t even being used as a constructor. It’s just assigning some properties to the object it was given by the “call” method. That leaves us to require more instruction to complete the subclassing:
Swordsman.prototype = Object.create(Combatant.prototype);
Swordsman.prototype.constructor = Swordsman;
To complete the rewiring, we assign the appropriate objects to their appropriate designations, so “Swordsman” instances can have the same structure as normally created instances of non-subclasses.
Oh, yeah, now we can continue with method assignments to complete the characterization of the subclass:
Swordsman.prototype.swing = function (){
return "Swings " + this.weapon;
}
Whew! That’s what’s required for Classical Subclassing in JavaScript. And still without the benefit of any encapsulation for the process.
But good news; ES6 has provided some syntax to make all this easier — kinda. We’ll explore that and how it all compares to object composition in part two. See ya there!
*classes in JavaScript aren’t actually quite classes since it is prototype-based rather than class-based. JavaScript’s approach to class is more accurately known as “pseudoclassical”, but for the purpose of this article, we can give JavaScript a break and call class methods as classical since it’s trying so hard.