JavaScript oop: Designing classes correctly

688 views Asked by At

Taking this list of similar questions:

  1. How to set up JavaScript namespace and classes properly
  2. Javascript namespace declaration with function-prototype
  3. Best OOP approach to these two small JavaScript classes

I'd concluded there are two possible ways for implementing classes and instances in JS: using an inner function or using a prototype.
So let's say we have a Box class inside the namespace BOX_LOGIC with a simple code in it. I'm able to code the following:

BOX_LOGIC.Box = (function() {
    // private static
    var boxCount = 0;

    var classDefinition = function(x) {
        x = x || 0;
        var capacity = x;
        var id = ++boxCount;

        // public methods
        this.getCapacity = function() { return capacity; };
        this.getId = function() { return id; };
        this.add = function(weight) { 
            weight = weight || 0;
            if (capacity >= weight) {
                capacity -= weight;
            }   
            return capacity;
        };
    };
    return classDefinition;
})();

As well as I'm able to code:

BOX_LOGIC.Box = (function () {
    var boxCount;

    var Box= function (x) {
        x = x || 0;
        this.capacity = x;
        this.id = ++boxCount;
    };

    Box.prototype = {
        Constructor: Box,
        add: function (weight) {
            weight = weight || 0;
            if (this.capacity >= weight) {
                this.capacity -= weight;
            }   
            return this.capacity;
        }
    };
    return Box;
})();

Mi question are: what is exactly the difference in using the Box prototype or not? is any approach better for any reason(cost, legibility, standard...)? Is in the second approach any way to emulate the static idvariable? THX!

1

There are 1 answers

7
T.J. Crowder On BEST ANSWER

Mi question are: what is exactly the difference in using the Box prototype or not?

is any approach better for any reason(cost, legibility, standard...)?

Functions (and other properties) on the prototype are shared between instances; if you create them in your constructor function, each instance gets its own copy of all of those functions.

The primary benefit is that you can add to the prototype, and even instances that already exist see the additions, since they use the prototype dynamically. In particular, this can help with aspect-oriented programming and various debugging and logging techniques, because you can dynamically wrap the functions on the prototype to capture calls. You can't do that when every instance has its own (unless you have a reference to every instance, which is unlikely).

In theory, using the prototype also means lower memory consumption. In practice, you'd have to have millions of instances to care, and modern engines are good at reusing the underlying code of the functions even though the function objects involved are distinct.

So unless you're going to augment the prototypes dynamically, one or the other is largely a matter of style.

Is in the second approach any way to emulate the static id variable?

I wouldn't have called it "static;" it's private to each instance. There are various ways to get close to private information with the prototype approach (that is, nearly-private information prototypical functions can access), but it's impossible to get truly get private information prototypical functions can access. There probably will be in ES7 (not ES6, which is currently being finalized; ES7). I address one of those near-private mechanisms in this blog post. The ES6 info in that post is now out of date; privacy stuff got pushed back to ES7, and "private Name" objects got morphed into Symbol which doesn't provide any real privacy at all.

I should flag up your third option, which you can use now with an ES6 transpiler: ES6's class:

// This is similar to the prototype version; `getCapacity` and
// `add` end up on `Box.prototype`
BOX_LOGIC.Box = (function () {

    class Box {
        constructor() {
            x = x || 0;
            this.capacity = x;
        }

        getCapacity() {
            return this.capacity;
        }

        add(weight) {
            weight = weight || 0;
            if (this.capacity >= weight) {
                this.capacity -= weight;
            }   
            return this.capacity;
        }
    }

    return Box;
})();

Side note: Since you used the word "correctly" in your title, I'll flag up a quite minor thing on your prototype example: You're messing up the constructor property. By default, Box.prototype has a property, constructor, that refers back to Box. But by replacing the object on Box.prototype with a completely different object, you're removing that. To be consistent with standard functions, I'd modify it like this:

Box.prototype = {
    constructor: Box,
    //...the existing stuff goes here...
};

Does it matter? Only if you end up with code that relies on the constructor property (and some libraries may). Nothing in JavaScript itself does, even though the JavaScript spec defines the property.