Prototypical OO in JavaScript Prototypical OO in JavaScript javascript javascript

Prototypical OO in JavaScript


I don't think the constructor/factory logic is necessary at all, as long as you change how you think about Object-Oriented Programming. In my recent exploration of the topic, I've discovered that Prototypical inheritance lends itself more to defining a set of functions that use particular data. This isn't a foreign concept to those trained in classical inheritance, but the hitch is that these "parent" objects don't define the data to be operated on.

var animal = {    walk: function()    {        var i = 0,            s = '';        for (; i < this.legs; i++)        {            s += 'step ';        }        console.log(s);    },    speak: function()    {        console.log(this.favoriteWord);    }}var myLion = Object.create(animal);myLion.legs = 4;myLion.favoriteWord = 'woof';

So, in the above example, we create the functionality that goes along with an animal, and then create an object that has that functionality, along with the data necessary to complete the actions. This feels uncomfortable and odd to anyone who's used to classical inheritance for any length of time. It has none of the warm fuzziness of the public/private/protected hierarchy of member visibility, and I'll be the first to admit that it makes me nervous.

Also, my first instinct, when I see the above initialization of the myLion object is to create a factory for animals, so I can create lions, and tigers, and bears (oh my) with a simple function call. And, I think, that's a natural way of thinking for most programmers - the verbosity of the above code is ugly, and seems to lack elegance. I haven't decided whether that's simply due to classical training, or whether that's an actual fault of the above method.

Now, on to inheritance.

I have always understood inhertance in JavaScript to be difficult. Navigating the ins and outs of the prototype chain is not exactly clear. Until you use it with Object.create, which takes all the function-based, new-keyword redirection out of the equation.

Let's say we wanted to extend the above animal object, and make a human.

var human = Object.create(animal)human.think = function(){    console.log('Hmmmm...');}var myHuman = Object.create(human);myHuman.legs = 2;myHuman.favoriteWord = 'Hello';

This creates an object which has human as a prototype, which, in turn, has animal as a prototype. Easy enough. No misdirection, no "new object with a prototype equal to the prototype of the function". Just simple prototypal inheritance. It's simple, and straightforward. Polymorphism is easy, too.

human.speak = function(){    console.log(this.favoriteWord + ', dudes');}

Due to the way the prototype chain works, myHuman.speak will be found in human before it's found in animal, and thus our human is a surfer instead of just a boring old animal.

So, in conclusion (TLDR):

The pseudo-classical constructor functionality was kind of tacked on to JavaScript to make those programmers trained in classical OOP more comfortable. It is not, by any means, necessary, but it means giving up classical concepts such as member visibility and (tautologically) constructors.

What you get in return is flexibility, and simplicity. You can create "classes" on the fly - every object is, itself, a template for other objects. Setting values on child objects will not affect the prototype of those objects (i.e. if I used var child = Object.create(myHuman), and then set child.walk = 'not yet', animal.walk would be unaffected - really, test it).

The simplicity of inheritance is honestly mind-boggling. I've read a lot on inheritance in JavaScript, and written many lines of code attempting to understand it. But it really boils down to objects inherit from other objects. It's as simple as that, and all the new keyword does is muddle that up.

This flexibility is difficult to use to its fullest extent, and I'm sure I have yet to do it, but it's there, and it's interesting to navigate. I think most of the reason that it hasn't been used for a large project is that it simply isn't understood as well as it could be, and, IMHO, we're locked into the classical inheritance patterns we all learned when we were taught C++, Java, etc.

Edit

I think I've made a pretty good case against constructors. But my argument against factories is fuzzy.

After further contemplation, during which I've flip-flopped to both sides of the fence several times, I have come to the conclusion that factories are also unnecessary. If animal (above) were given another function initialize, it would be trivial to create and initialize a new object that inherits from animal.

var myDog = Object.create(animal);myDog.initialize(4, 'Meow');

New object, initialized and ready for use.

@Raynos - You totally nerd sniped me on this one. I should be getting ready for 5 days of doing absolutely nothing productive.


As per your comment that the question is mainly "is constructor knowledge necessary?" I feel it is.

A toy example would be storing partial data. On a given data set in memory, when persisting I may only choose to store certain elements (either for the sake of efficiency or for data consistency purposes, e.g. the values are inherently useless once persisted). Let's take a session where I store the user name and the number of times they've clicked on the help button (for lack of a better example). When I persist this in my example, I do have no use for the number of clicks, since I keep it in memory now, and next time I load the data (next time the user logs in or connects or whatever) I will initialise the value from scratch (presumably to 0). This particular use case is a good candidate for constructor logic.

Aahh, but you could always just embed that in the static prototype: Object.create({name:'Bob', clicks:0}); Sure, in this case. But what if the value wasn't always 0 at first, but rather it was something that required computation. Uummmm, say, the users age in seconds (assuming we stored the name and the DOB). Again, an item that there is little use persisting, since it will need to be recalculated on retrieval anyway. So how do you store the user's age in the static prototype?

The obvious answer is constructor/initialiser logic.

There are many more scenarios, although I don't feel the idea is much related to js oop or any language in particular. The necessity for entity creation logic is inherent in the way I see computer systems model the world. Sometimes the items we store will be a simple retrieval and injection into a blueprint like prototype shell, and sometimes the values are dynamic, and will need to be initialised.

UPDATE

OK, I'm going to try for a more real-world example, and to avoid confusion assume that I have no database and need not persist any data. Let's say I'm making a solitaire server. Each new game will be (naturally) a new instance of the Game prototype. It is clear to me that their is initialiser logic required here (and lots of it):

I will, for example, need on each game instance not just a static/hard-coded deck of cards, but a randomly shuffled deck. If it were static the user would play the same game every time, which is clearly not good.

I may also have to start a timer to finish the game if the player runs out. Again, not something that can be static, since my game has a few requirements: the number of seconds is inversely related to the number of games the connected player has won so far (again, no saved info, just how many for this connection), and proportional to the difficulty of the shuffle (there is an algorithm that according to the shuffle results can determine the degree of difficulty of the game).

How do you do that with a static Object.create()?


Example of a staticly-clonable "Type":

var MyType = {  size: Sizes.large,  color: Colors.blue,  decay: function _decay() { size = Sizes.medium },  embiggen: function _embiggen() { size = Sizes.xlarge },  normal: function _normal() { size = Sizes.normal },  load: function _load( dbObject ) {     size = dbObject.size    color = dbObject.color   }}

Now, we could clone this type elsewhere, yes? Sure, we would need to use var myType = Object.Create(MyType), but then we're done, yes? Now we can just myType.size and that is the size of the thing. Or we could read the color, or change it, etc. We haven't created a constructor or anything, right?

If you said there's no constructor there, you're wrong. Let me show you where the constructor is:

// The following var definition is the constructorvar MyType = {  size: Sizes.large,  color: Colors.blue,  decay: function _decay() { size = Sizes.medium },  embiggen: function _embiggen() { size = Sizes.xlarge },  normal: function _normal() { size = Sizes.normal },  load: function _load( dbObject ) {     size = dbObject.size    color = dbObject.color   }}

Because we've already gone and created all the things we wanted and we've already defined everything. That's all a constructor does. So even if we only clone/use static things (which is what I see the above snippets as doing) we've still had a constructor. Just a static constructor. By defining a type, we have defined a constructor. The alternative is this model of object construction:

var MyType = {}MyType.size = Sizes.large

But eventually you're going to want to use Object.Create(MyType) and when you do, you will have used a static object to create the target object. And then it becomes the same as the previous example.