How JavaScript Inheritance could have been
JavaScript could have been so much more simpler if it had just one extra thing, and got rid of the new operator, and here's why.
function Animal(name){ return { parent:: Animal.blueprint, name: name }; } Animal.blueprint = { name: 'unknown', speak: function(){ alert("Grrrrrrrrrrr!"); } }; var instance = Animal('bob'); instance.speak(); // "Grrrrrrrrrr!" instance.name === 'bob'; // true function Human(){ return { parent:: Human.blueprint, name: name }; } // Human blueprine Human.blueprint = { // Inherit from Animal parent:: Animal.blueprint, speak: function(){ alert("Hi, I'm " + Human.blueprint.name); // Notice that Human.blueprint.name isn't defined, but inherited from Animal.blueprint } } // Or you could have used a syntactic surgary method to extend Human.blueprint = extend(Animal.blueprint, { speak: function(){ alert("Hi, I'm " + Human.blueprint.name); } }); function extend(parent, child){ child..parent = parent; return child; } var instance = Human('bob'); instance.speak(); // "Hi, I'm bob!" instance.name === 'bob'; // true
Above you'll notice this weird object literal syntax where you have two colons instead of one. And you'll also notice two dots for property accessing instead of one. What is this? Well, first let's talk about the infamous prototype in JavaScript.
The prototype of an object is another object that the JavaScript engine keeps track of for each object. It's stored in what the ECMAScript standard calls "slots". Whenever a property isn't found in an object, the JavaScript engine looks at the object's prototype for the property, and if it's found returns the value, otherwise it looks at the prototype's prototype, and repeats the process on and on up the prototype "chain" until it finds a defined property or doesn't find one at all.
These slots are hidden from the programmer, however some non-standard implementations of a __proto__ property for each object have made the prototype slot accessible. This is bad for one reason which I'll get to later. But, there is a reason why I think being able to explicitly access and define a slot is a good idea.
Which brings me back around to the double colons and dots. If you could define object "slots" or "meta properties" on an object, then we'd have the power of __proto__ without the bad part, which is: the properties for an object would be clear of any implementation details, therefore you could still use obj.__proto__ for your own uses without effecting the object's meta data.
Great, so let's talk about naming conventions. The whole purpose of the prototype is so objects can inherit properties from other objects. So why call it prototype? Well, it fits the definition, but I say it doesn't have to be a technical word the newcomers have to learn and understand (I believe the name itself acts in part of the confusion to learning JavaScript). I would purpose that the object's prototype be called it's parent. So instead of obj..prototype = otherObject, it would be obj..parent = otherObject. "parent" is easier to type and feels cleaner.
So what about the new operator, why get rid of it? Well, all it does is call a function with this bound to the function's prototype property and returns this. I bet that if you're struggling with JavaScript, you didn't understand a lick of what i just said, or at least it took you a while to wrap your head around. I say we don't need all this confusion. I say you give access to the meta properties (slots) of an object to the programmer and let them define what the parent meta property should be of an object for inheritance. Done.
Now we could add sugar and conventions to our code that would mimic whatever new operator pattern that was trying to be mimic from Java in the first place. All we'd need is a function that returns an object with a defined parent.
function newMyObject(prop) { return { parent:: newMyObject.blueprint, property: prop } } newMyObject.blueprint = { property: "Default value" } var instance = newMyObject();
Remember that :: means define a meta property, in this case the parent meta property.
We call it newMyObject as a convention for "constructor functions" like this. We could follow another convention where we just make the first letter of the identifier uppercase, i.e. "MyObject".
What's newMyObject.blueprint? Another convention. It's just a property of newMyObject that's used to define the parent object of each instance that newMyObject returns. We could have called it newMyObject.prototype or newMyObject.parent, but I feel like either of those would have been confusing. The identifier blueprint seems to convey what it's trying to accomplish, which is to be the blueprint for each instance.
In classical OO programming we have these "classes" that act as blueprints to instances. In a similar way, we have an object that acts as a blueprint to instances. There are no classes in JavaScript, just objects. So this convention makes more since to a classical OO programmer. However, the coder is not limited to using these conventions.
So. All this makes it simpler, because now when you teach JavaScript to people, all they need to "get" is the meta properties and specifically the parent meta property. Once they understand this concept, they can go ahead a recklessly create any objects and patterns to their hearts content. However, you might want to teach them some good practices and patterns.
Look at the above code at the beginning of this post for another pattern that people could use, the extend function. Go a head and see if you could figure it out on your own because I already taught you about the parent meta property. The extend function isn't needed though, it's just some syntactic sugar.
Final note: Whether meta properties should be constant or not is subject to good discussion. I personally think that meta properties should be variable, but there may be good reason to making your script error out if you attempt to define an already defined meta property. Let me know what the argument for constants is.