Still ES5, the most prolific version of the language, can be quite tricky. In fact, it reminds me of those persistent deadites from Evil Dead series. Just when you think you've got it beat, some unholy aspect of the language rears its putrid, moldering head causing bugs and making your life unpleasant. Things like variable hoisting, the handling of the this keyword, and closures all are constant headaches for developers everywhere. But one aspect of the language that I used to find particularly treacherous is the way that inheritance works. What is prototypical inheritance? What that's __proto__ thing I keep seeing? Where does the Object base class fit in?
It's objects all the way down
But did you know that functions are objects too? Try this:
FYI, arrays are also objects. Pretty much everything except primitive types (strings, numbers, booleans) are objects.
That prototype thing
Here we are adding a property called numBarrels and a function called fire to the prototype property hanging off the Shotgun function (I mean object). Notice that after creating the function and immediately logging the prototype object to the console, we do not get undefined. It's an empty object, but it's an object nonetheless! Of course after adding some properties to it, we see the those echoed back on the next call to console.log().
The important thing to remember here is that all functions are objects and they come with a built in property called prototype that points to an empty object.
That other __proto__ thing
You can see it's basically just an empty object (it's actually not empty but everything on it an internal property).
These all run fine and are perfectly valid properties to access through that object. But if we didn't define them, how did they get there? Well it turns out they are defined on the built in Object.prototype object. Try this:
You will see toString, constructor, and several more.
"Okay", you say, "but what does that have to do with the object literal o above?"
Adding properties to Object.prototype is one way you can add new functionality to all your objects. If you add a new property called necronomicon to Object.prototype, that property will now show up on all objects.
As it turns out, you can even make an object's __proto__ point to any old random object (or null). Just for fun try this:
Now before we move on, I'll get nerd slapped hard if I don't warn you that changing the __proto__ property on an object is dangerous so I don't recommend actually doing that outside of educational purposes.
Ways to create things
That's great, but what about when people create objects from classes they wrote?
You can verify this by running the following code:
There's one more way to create objects that I want to cover, the user of Object.create():
What's going on there? Well as it turns out that is quite relevant to our discussion because what this method does is to create an object and set its __proto__ pointer to whatever object you pass in:
It has the same effect of setting o.__proto__ to alt. Neato, so what's this have to do with inheritance?
For your consideration, a class called Shotgun:
Now here is a class that derives from Shotgun:
If you've read this far, then probably wondering about this line:
Well, that's the line where all the rich, creamy inheritance happens. Remember that that Object.create() creates an object and sets its __proto__ property to whatever object you pass in. Well here we are passing in the Shotgun.prototype object. That links the __proto__ pointer inside the Boomstick.prototype object to the Shotgun.prototype object. And since we did not modify Shotgun.prototype, it still points to Object.prototype.
"Wait, what? The prototype object has a __proto__ property on it?"
So here we have a class Shotgun that defines two methods, fire and reload, and a class Boomstick that inherits reload and overrides fire.
Now when you call splatterGun.reload(), the engine won't find reload on splatterGun or on Boomstick.prototype. So it follows Boomstick.prototype.__proto__ up to Shotghun.prototype where it does find reload and executes it.
If you read nothing else, read this!
I'll close with a fun story about Bruce Campbell, star of the Evil Dead universe. In the late 90's I had a buddy that worked at Activision. At the time they were rebooting Pitfall on the PlayStation and they tapped old Bruce to be the voice of Pitfall Harry. Back then, having live voice acting in video games was considered novel (sometimes to hilarious effect). One day my buddy was walking down the hall at Activision and who should he see coming the other direction but Bruce Campbell himself! My friend does a double take and utters "No way!" to which Bruce replies "In a big way baby!".
God bless that man.