Prototypes in JavaScript
JavaScript has prototypes, and prototypes are weird. So weird, in fact, that some languages (like CoffeeScript) that compile down to JavaScript try to paper over this fact in an attempt to present a more wholesome, easily digestible package. However, once you learn to use prototypes they can be an amazingly useful tool in your arsenal.
Classless Society
So you’re in your studio, making the latest in your hits series of games, “Call of Murder.” You’ve already made like eight of these, so you decide to change it up and code the whole thing in JavaScript. Easy enough, right? However there’s this little problem – JavaScript said it was object-oriented, but you can’t figure out how to define a class. As it turns out, you can’t use classes! At least not in the traditional sense. But there’s definitely objects.
Your First Object
soldier = new Object()
The previous code creates an object and calls it soldier
. What do you want the soldier to do? How about jumping? You can map the A
button to a jump()
function that you’ve defined elsewhere, as shown below.
soldier.a = jump
You’re essentially setting the a
property on the soldier to be equal to a function. Notice that we didn’t include parentheses. That’s because when you include parentheses with a function in JavaScript, it calls that function. If you don’t include the parentheses, it just returns the a reference to the function itself. So, if you call soldier.a()
, it will execute the function. But, if you write soldier.a
without the parentheses, then it will return the actual function.
Also, take note that you are assigning a function directly on an object. What we’ve noted so far are quirks of JavaScript, but not directly related to the prototype system. So, given that you’re fairly experienced with scripting in JavaScript you move on and assign functions to the other buttons, as shown below.
soldier.b = punch
soldier.x = reload
soldier.r = machineGun
Copy-Paste Hell
Now, you want to create a second player who is almost exactly like the first one, except that they use a sniper rifle instead of a machine gun. There are two ways of doing this. The first is to repeat the code, as shown below.
sniper = new Object()
sniper.a = jump
sniper.b = punch
sniper.x = reload
sniper.r = snipe
It works, but it’s tedious. Especially since you promised the game designer that he could put 100 different types of characters in the game, each with different combinations of the same 200 moves. That’s a lot of repeated code, a lot of copy-paste, and a lot of potential bugs.
Rescued by Prototypes
So, you recode your sniper using an object()
function, as shown below. We’ll come back to the actual implementation of object()
.
sniper = object(soldier)
sniper.r = snipe
You load up your game just to make sure, and start playing as a sniper. Sure enough, the A
button makes your character jump. You press the X
button, and it reloads. You press the R
button, and it shoots the sniper rifle. It appears that the sniper has inherited all the traits from soldier, then overwritten the function mapped to R
. If you were explaining it in English, you would say “a sniper is like a soldier, except it shoots a sniper rifle.” Notice the similarity of the English sentence to the code defining the sniper.
Function Theft
Let’s implement a wounded sniper. They’re like a normal sniper, except when they try to jump they cry out in pain.
woundedSniper = object(sniper)
woundedSniper.a = function(){console.log('aaaargh my leg!')}
Here, we used an anonymous function instead of a previously named function. That’s fine, but it means we’ll have to do something a bit different when we’re defining the woundedSoldier
class.
woundedSoldier = object(soldier)
woundedSoldier.a = woundedSniper.a
Did you see that? You stole a function right off of another object! Try doing that in a regular object-oriented language. I dare you! (please note: this is an actual dare. I would not be surprised if someone accomplished it, but I would be surprised if it was as easy or as beautiful as this.) Now it’s time to look at the object()
function.
Behind the Curtain
function object(o){
function F(){}
F.prototype = o;
return new F();
}
object()
takes in one argument (o
, or what we might traditionally refer to as the parent object). It creates a dummy “class” function F()
, complete with an empty constructor. It sets the prototype
property of the class to the argument you passed in. Then, it returns an instance of the dummy class. Douglas Crockford has officially endorsed the object(o)
method over the class system. In fact, that’s where we got the function.
The prototype
Property
The one lingering question you’re left with is, what is that prototype
property. Maybe it could be useful in your own code? If you print F.prototype
to the console when creating a sniper, you get the following output:
{ a: [Function], b: [Function], x: [Function], r: [Function] }
You’ll recognize these as the mappings we made from the various buttons to functions. This is to be expected, because we’ve assigned the soldier object to the prototype of the dummy class F
. However, we then create an instance of the dummy class and assign it to the sniper. If you ask what the prototype of sniper is, you get undefined
. What happened? And how does sniper know what to do when you hit the X
button?
Even more weirdness – when you are creating woundedSniper
, the prototype of F
only returns a mapping to the R
button. But, clearly the woundedSniper
can reload and punch. How do we explain all this?
__proto__
Confusingly, there are two prototype properties. When the constructor is called, prototype
is assigned to the __proto__
property. sniper.__proto__
returns the following:
{ a: [Function], b: [Function], x: [Function], r: [Function] }
Even more interestingly, sniper.__proto__
has a __proto__
of its own. sniper.__proto__.__proto__
gives you the functions that soldier
inherited from Object
. Meanwhile, prototype
is simply used in the constructor to set the __proto__
property on the actual object.
Conclusion
With our use of the object(o)
function and our discovery of __proto__
, we see that prototypical code can do as much as classical object-oriented code. The ability to define unique instances on the fly, as well as the ability to transfer a function directly from one instance to another at any time, make it ultimately more powerful.
Further Reading
This article was partially inspired by Steve Yegge’s blog post/book. Read that article, and keep an eye out for an upcoming post on how CoffeeScript creates a traditional class system out of JavaScript prototypes.