Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prototypical OO in JavaScript

TL;DR:

Do we need factories/constructors in prototypical OO? Can we make a paradigm switch and drop them completely?

The BackStory:

I've been toying with doing prototypical OO in JavaScript lately and find that 99% of OO done in JavaScript is forcing classical OO patterns into it.

My take on prototypical OO is that it involves two things. A static prototype of methods (and static data) and a data binding. We don't need factories or constructors.

In JavaScript these are Object literals containing functions and Object.create.

This would mean we can model everything as a static blueprint/prototype and a data binding abstraction that's preferably hooked straight into a document-style database. I.e. objects are taken out of the database and created by cloning a prototype with the data. This would mean there is no constructor logic, no factories, no new.

The Example code:

An pseudo example would be :

var Entity = Object.create(EventEmitter, {
    addComponent: {
        value: function _addComponent(component) {
            if (this[component.type] !== undefined) {
                this.removeComponent(this[component.type]);
            }

            _.each(_.functions(component), (function _bind(f) {
                component[f] = component[f].bind(this);
            }).bind(this));

            component.bindEvents();

            Object.defineProperty(this, component.type, {
                value: component,
                configurable: true
            });

            this.emit("component:add", this, component);
        }
    },
    removeComponent: {
        value: function _removeComponent(component) {
            component = component.type || component;

            delete this[component];

            this.emit("component:remove", this, component);
        }
    }
}

var entity = Object.create(Entity, toProperties(jsonStore.get(id)))

The minor explanation:

The particular code is verbose because ES5 is verbose. Entity above is a blueprint/prototype. Any actual object with data would be created by using Object.create(Entity, {...}).

The actual data (in this case the components) is directly loaded from a JSON store and injected directly into the Object.create call. Of course a similar pattern is applied to creating components and only properties that pass Object.hasOwnProperty are stored in the database.

When an entity is created for the first time it's created with an empty {}

The actual Questions:

Now my actual questions are

  • Open source examples of JS prototypical OO?
  • Is this a good idea?
  • Is it in-line with the ideas and concepts behind prototypical OOP?
  • Will not using any constructors/factory functions bite me in the ass somewhere? Can we really get away with not using constructors. Are there any limitations using the above methodology where we would need factories to overcome them.
like image 824
Raynos Avatar asked Jun 29 '11 19:06

Raynos


People also ask

What is OO in JavaScript?

a style of Object-oriented programming (OOP) in which inheritance occurs via defining classes of objects, instead of inheritance occurring via the objects alone. The most popular model of OOP is class-based. But as I mentioned, JavaScript isn't a classed-based langauge – it's is a prototype-based langauge.

What is a prototype object in JavaScript?

The prototype is an object that is associated with every functions and objects by default in JavaScript, where function's prototype property is accessible and modifiable and object's prototype property (aka attribute) is not visible. Every function includes prototype object by default. Prototype in JavaScript.

What is prototype in JavaScript with example?

In JavaScript, every function and object has a property named prototype by default. For example, function Person () { this.name = 'John', this. age = 23 } const person = new Person(); // checking the prototype value console.

What is prototypal oo?

Prototype-based programming is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming.


3 Answers

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.

like image 187
Ryan Kinal Avatar answered Sep 22 '22 21:09

Ryan Kinal


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()?

like image 35
davin Avatar answered Sep 19 '22 21:09

davin


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 constructor
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 
  }
}

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.

like image 21
jcolebrand Avatar answered Sep 19 '22 21:09

jcolebrand