Last time, I got started with JavaScript by doing the Roman Numerals kata.
I got the kata to work, but like all first steps, it felt awkward. The main reason is that JavaScript has a different object model than I’m used to.
Let’s suit up and shine some light on that model.
Objects
Things in JavaScript are either primitives or objects.
Objects can be created using literals:
var romanNumeral = { symbol: "i", value: 1 };
A new object can also be created by the new
operator and a constructor. The constructor can refer to the newly created object with this
:
function RomanNumeral(symbol, value) { this.symbol = symbol; this.value = value; }
In JavaScript, an object represents a table relating names to values.
The constructor above relates the name
string to the object provided in the name
parameter. (Let’s hope that object is actually a string.)
Name and value together are referred to as a property.
Values are things again, so either primitives or objects. Functions are objects too, as we’ll see below.
Here’s how someone with a Java background like me might initially try to code a JavaBean-like object:
function RomanNumeral(symbol, value) { this.symbol = symbol; this.value = value; this.getSymbol = function() { return this.symbol; }; this.getValue = function() { return this.value; }; }
There are some problems with this piece of code, however.
Methods
The first issue with the JavaBean-like code is that it’s built on the mistaken assumption that the
symbol
and value
properties are private.
The properties of a JavaScript object are automatically exposed. Nobody is blind to your internals in JavaScript!
Luckily, JavaScript does provide a reliable mechanism for information hiding, namely the closure:
function RomanNumeral(symbol, value) { this.symbol = function() { return this.symbol; }; this.value = function() { return value; }; }
Here the value of the symbol
property is a function rather than a string. Functions in JavaScript are first-class citizens and can be passed around like any other object and then be called later.
Functions can refer to any variable in their scope, including the parameters and variables of outer functions.
So the closure assigned to the symbol
property can refer to the symbol
parameter provided to the constructor even when that parameter is out of scope at the place the closure is actually called!
Class Methods vs Instance Methods
The second problem with the initial code, and also with the improved code above, is that it creates new function objects and assigns them to the object’s properties every time an instance is created.
In the closure case, that is actually what we want, since the closure should have the constructor’s parameters in its scope for it to work properly.
In the original code, however, we end up with too many function objects. There will be one getSymbol
function object per instance, for example. We can reduce that overhead by defining the function on the prototype:
function RomanNumeral(symbol, value) { this.symbol = symbol; this.value = value; } RomanNumeral.prototype.getSymbol = function() { return this.symbol; }; RomanNumeral.prototype.getValue = function() { return this.value; };
Every object is associated with a prototype object. The
prototype
property is set automatically by the constructor.
With the above code, all objects created with new RomanNumeral(...)
still have their own symbol
property.
But now they all share the same instance of the getSymbol()
function, because they access it through the prototype
property that points to a separate object.
We can use the same trick with non-function properties too:
function RomanNumerals() { // ... } RomanNumerals.prototype.ROMAN_NUMERALS = [ // ... other numerals ... new RomanNumeral("iv", "4"), new RomanNumeral("i", "1") ];
This is analogous to static
variables in Java.
Subclasses
Let’s leave the Roman numerals behind and move into more interesting territory. Superheros have the ability to display their superpowers:
function SuperHero(name) { this.name = name; } SuperHero.prototype.showPowers = function() { beAwesome(); };
Some superheros can fly and therefore have an altitude:
function FlyingSuperHero(name) { SuperHero.call(name); this.altitude = 0; } FlyingSuperHero.prototype = Object.create( SuperHero.prototype); FlyingSuperHero.prototype.flyTo = function(altitude) { this.altitude = altitude; };
Here we see some very powerful things at work.
First, a function is an object and can therefore have properties. The call()
method is one such property.
Second, prototype
is a property too, and can be set! We use this to create a new object with its prototype set to the object that represents the base class’ prototype.
Note that since objects are basically hash tables, we can’t simply override showPowers
and call the super class’ version. There are some ways to achieve that, but they don’t look pretty.
This goes to show that you can’t force the Java model onto JavaScript without pain. To be successful in JavaScript, you must embrace its object model.
Reflection
It will probably take me a while to get used to JavaScript’s different object model.
I freaked out when I first realized that any code can change any property and that different instances of a “class” can have different methods.
Coming from a strongly typed world, that seems great power that is easy to abuse.
Better handle that superpower wisely!