1

Background

I decided I would practice by making a simple calculator app in JS. The first step was to implement a stack class. I ran into some problems however in achieving data encapsulation with the revealing prototype pattern (?). Here's how it looks right now:

Stack "class":

var Stack = (function () { 

    var Stack = function() {
        this.arr = []; // accessible to prototype methods but also to public
    };

    Stack.prototype = Object.prototype; // inherits from Object

    Stack.prototype.push = function(x) {
        this.arr.push(x);
    };

    Stack.prototype.pop = function() {
        return this.arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null;
    };

    Stack.prototype.size = function() {
        return this.arr.length;
    };

    Stack.prototype.empty = function() {
        return this.arr.length === 0;
    };

    return Stack;

})();

Test code:

var s1 = new Stack();
var s2 = new Stack();

for(var j = 1, k = 2; j < 10, k < 11; j++, k++) {

  s1.push(3*j);
  s2.push(4*k);

}

console.log("s1:");
while(!s1.empty()) console.log(s1.pop());
console.log("s2:");
while(!s2.empty()) console.log(s2.pop());

The Problem

The only problem is that the arr is accessible. I would like to hide the arr variable somehow.

Attempts at a Solution

My first idea was to make it a private variable like Stack:

var Stack = (function () {

    var arr = []; // private, but shared by all instances

    var Stack = function() { };
    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        arr.push(x);
    };

    // etc.

})();

But of course this approach doesn't work, because then the arr variable is shared by every instance. So it's a good way of making a private class variable, but not a private instance variable.

The second way I thought of (which is really crazy and definitely not good for readability) is to use a random number to restrict access to the array variable, almost like a password:

var Stack = (function() {

    var pass = String(Math.floor(Math.pow(10, 15 * Math.random()));
    var arrKey = "arr" + pass;

    var Stack = function() {
        this[arrKey] = []; // private instance and accessible to prototypes, but too dirty
    };

    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        this[arrKey].push(x);
    };

    // etc.

})();

This solution is... amusing. But obviously not what I want to do.

The last idea, which is what Crockford does, allows me to create a private instance member, but there's no way I can tell to make this visible to the public prototype methods I'm defining.

var Stack = (function() {

    var Stack = function() {
        var arr = []; // private instance member but not accessible to public methods
        this.push = function(x) { arr.push(x); }; // see note [1]
    }

})();

[1] This is almost there, but I don't want to have the function definitions within the var Stack = function() {...} because then they get recreated every time that an instance is created. A smart JS compiler will realize that they don't depend on any conditionals and cache the function code rather than recreating this.push over and over, but I'd rather not depend on speculative caching if I can avoid it.

The Question

Is there a way to create a private instance member which is accessible to the prototype methods? By somehow utilizing the 'bubble of influence' created by the enclosing anonymous function?

Chris Middleton
  • 5,654
  • 5
  • 31
  • 68
  • Just a note, Douglas Crockford considers `})();` dogballs, and recommends `}());` instead when wrapping functions in parenthesis. http://www.youtube.com/watch?v=eGArABpLy0k – Bjorn Oct 04 '14 at 16:41
  • You can't without creating a closure for every instance per privileged function. Here is a pattern for protected that has more code but less closures: http://stackoverflow.com/questions/21799353/pseudo-classical-inheritance-with-privacy/21800194#21800194 – HMR Oct 04 '14 at 23:22
  • @HMR I see now that my new answer below is basically the same as the one you linked. Shame I didn't understand it the first time you posted (could've saved myself some time!). – Chris Middleton Oct 05 '14 at 17:33

3 Answers3

1

You could use a factory function that creates an instance for you:

function createStack() {
    var arr = [];

    function Stack() {

    };

    Stack.prototype = Object.prototype; // inherits from Object

    Stack.prototype.push = function(x) {
        arr.push(x);
    };

    Stack.prototype.pop = function() {
        return arr.length ? (this.arr.splice(this.arr.length - 1, 1))[0] : null;
    };

    Stack.prototype.size = function() {
        return arr.length;
    };

    Stack.prototype.empty = function() {
        return arr.length === 0;
    };

    return new Stack();

}

You would be defining the class on every execution of the factory function, but you could get around this by changing this to define most of Stack outside the constructor function, like the parts that dont use arr could be further up the prototype chain. Personally I use Object.create instead of prototype now and I almost always use factory functions to make instances of these types of objects.

Another thing you could do is maintain a counter that keeps track of the instance and holds on to an array of arrays.

var Stack = (function() {

    var data = [];


    var Stack = function() {
        this.id = data.length;
        data[this.id] = [];
    };

    Stack.prototype = Object.prototype;

    Stack.prototype.push = function(x) {
        data[this.id].push(x);
    };

    // etc.

}());

Now you have the hidden data multi dimensional array, and every instance just maintains its index in that array. You have to be careful to manage the memory now though, so that when your instance isn't being used anymore you remove what's in that array. I don't recommend doing it this way unless you are disposing your data carefully.

Bjorn
  • 69,215
  • 39
  • 136
  • 164
1

A real solution

EDIT: It turns out this solution is basically the same as the one described here, first posted by HMR in a comment to my question above. So definitely not new, but it works well.

var Stack = (function Stack() {

    var key = {};

    var Stack = function() {
        var privateInstanceVars = {arr: []};
        this.getPrivateInstanceVars = function(k) {
            return k === key ? privateInstanceVars : undefined;
        };
    };

    Stack.prototype.push = function(el) {
        var privates = this.getPrivateInstanceVars(key);
        privates.arr.push(el);
    };

    Stack.prototype.pop = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length ? privates.arr.splice(privates.arr.length - 1, 1)[0] : null;
    };

    Stack.prototype.empty = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length === 0;
    };

    Stack.prototype.size = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.length;
    };

    Stack.prototype.toString = function() {
        var privates = this.getPrivateInstanceVars(key);
        return privates.arr.toString();
    };

    Stack.prototype.print = function() {
        var privates = this.getPrivateInstanceVars(key);
        console.log(privates.arr);
    }

    return Stack;

}());


// TEST

// works - they ARE separate now
var s1 = new Stack();
var s2 = new Stack();
s1.push("s1a");
s1.push("s1b");
s2.push("s2a");
s2.push("s2b");
s1.print(); // ["s1a", "s1b"]
s2.print(); // ["s2a", "s2b"]

// works!
Stack.prototype.push.call(s1, "s1c");
s1.print(); // ["s1a", "s1b", "s1c"]


// extending the Stack

var LimitedStack = function(maxSize) {

  Stack.apply(this, arguments);
  this.maxSize = maxSize;

}

LimitedStack.prototype = new Stack();
LimitedStack.prototype.constructor = LimitedStack;

LimitedStack.prototype.push = function() {

  if(this.size() < this.maxSize) {
    Stack.prototype.push.apply(this, arguments);
  } else {
    console.log("Maximum size of " + this.maxSize + " reached; cannot push.");
  }

  // note that the private variable arr is not directly accessible
  // to extending prototypes
  // this.getArr(key) // !! this will fail (key not defined)

};

var limstack = new LimitedStack(3);

limstack.push(1);
limstack.push(2);
limstack.push(3);
limstack.push(4); // Maximum size of 3 reached; cannot push
limstack.print(); // [1, 2, 3]

Cons: basically none, other than remembering a little extra code

Original solution

(The first method originally posted was substantially different from what is below, but through some careless editing I seem to have lost it. It didn't work as well anyway, so no real harm done.)

Here a new object/prototype is created with every instantiation, but it borrows much of the code from the static privilegedInstanceMethods. What still fails is the ability to do Stack.prototype.push.call(s1, val), but now that the prototype is being set on the object, I think we're getting closer.

var Stack = (function() {

    var privilegedInstanceMethods = {
        push: function(x) { 
            this.arr.push(x);
        },
        pop: function() {
            return this.arr.length ? this.arr.splice(this.arr.length - 1, 1)[0] : null;
        },
        size: function() {
            return this.arr.length;
        },
        empty: function() {
            return this.arr.length === 0;
        },
        print: function() {
            console.log(this.arr);
        },
    };

    var Stack_1 = function() {
        var Stack_2 = function() {
            var privateInstanceMembers = {arr: []};
            for (var k in privilegedInstanceMethods) {
                if (privilegedInstanceMethods.hasOwnProperty(k)) {
                    // this essentially recreates the class each time an object is created,
                    // but without recreating the majority of the function code
                    Stack_2.prototype[k] = privilegedInstanceMethods[k].bind(privateInstanceMembers);
                }
            }
        };
        return new Stack_2(); // this is key
    };

    // give Stack.prototype access to the methods as well.
    for(var k in privilegedInstanceMethods) {
        if(privilegedInstanceMethods.hasOwnProperty(k)) {
            Stack_1.prototype[k] = (function(k2) {
                return function() {
                    this[k2].apply(this, arguments);
                };
            }(k)); // necessary to prevent k from being same in all
        }
    }

    return Stack_1;

}());

Test:

// works - they ARE separate now
var s1 = new Stack();
var s2 = new Stack();
s1.push("s1a");
s1.push("s1b");
s2.push("s2a");
s2.push("s2b");
s1.print(); // ["s1a", "s1b"]
s2.print(); // ["s2a", "s2b"]

// works!
Stack.prototype.push.call(s1, "s1c");
s1.print(); // ["s1a", "s1b", "s1c"]

Pros:

  • this.arr is not directly accessible
  • method code is only defined once, not per instance
  • s1.push(x) works and so does Stack.prototype.push.call(s1, x)

Cons:

  • The bind call creates four new wrapper functions on every instantiation (but the code is much smaller than creating the internal push/pop/empty/size functions every time).
  • The code is a little complicated
Community
  • 1
  • 1
Chris Middleton
  • 5,654
  • 5
  • 31
  • 68
  • That doesn't work, s1 and s2 are sharing the same thing there. Check it out: http://jsfiddle.net/btipling/59p8fs3g/ – Bjorn Oct 04 '14 at 22:10
  • This works: http://jsfiddle.net/btipling/59p8fs3g/1/ I'm going to think about it some more though. – Bjorn Oct 04 '14 at 22:16
  • My opinion is that it's an interesting idea, but it doesn't give you anything you couldn't get with a defineProperties argument. You're basically just making a non-writable property which you can already do with `defineProperties` with less work. A side effect of this is that the `this` in `privilegedInstanceMethods` is not the same `this` in the instance of stack so they can't share access to values. This might be useful if you wanted a subset of array features, but I can't think of a decent application. – Bjorn Oct 04 '14 at 22:27
  • You've basically made your instance a proxy for a second, separate object's methods. Seems like you could remove the complexity, create a new instance of that thing and assign it to the stack and refer to it as a property instead of proxying the methods directly. – Bjorn Oct 04 '14 at 22:31
  • I +1, I liked your direction of thinking. :D – Bjorn Oct 04 '14 at 23:09
  • @Bjorn - Wow, I must have been tired when I wrote that. I have no clue why I thought it worked. Whoops. Also, re: your second jsfiddle, I actually had that originally in my post, but then I tried to change it so that the `Stack.prototype.push.call(s1, x)` would also work, but clearly it can't. I'll update my answer to reflect that. – Chris Middleton Oct 05 '14 at 00:42
  • Thanks for all the input. I've updated it to reflect what I originally had (same as your second fiddle). I'll keep thinking about it, but it's probably impossible to get the prototype and private instance member to work together directly. – Chris Middleton Oct 05 '14 at 00:50
1

The short answer here, is that you can't have all things, without sacrificing a little.

A Stack feels like a struct of some kind, or at very least, a data-type which should have either a form of peek or read-access, into the array.

Whether the array is extended or not, is of course up to you and your interpretation...

...but my point is that for low-level, simple things like this, your solution is one of two things:

function Stack () {
    this.arr = [];
    this.push = function (item) { this.arr.push(item); }
    // etc
}

or

function Stack () {
    var arr = [];
    var stack = this;

    extend(stack, {
        _add  : function (item) { arr.push(item); },
        _read : function (i) { return arr[i || arr.length - 1]; },
        _remove : function () { return arr.pop(); },
        _clear  : function () { arr = []; }
    });
}

extend(Stack.prototype, {
    push : function (item) { this._add(item); },
    pop  : function () { return this._remove(); }
    // ...
});

extend here is just a simple function that you can write, to copy the key->val of objects, onto the first object (basically, so I don't have to keep typing this. or Class.prototype..

There are, of course, dozens of ways of writing these, which will all achieve basically the same thing, with modified styles.

And here's the rub; unless you do use a global registry, where each instance is given its own unique Symbol (or unique-id) at construction time, which it then uses to register an array... ...which of course, means that the key then needs to be publicly accessible (or have a public accessor -- same thing), you're either writing instance-based methods, instance-based accessors with prototyped methods, or you're putting everything you need in the public scope.

In the future, you will be able to do things like this:

var Stack = (function () {
    var registry = new WeakMap();

    function Stack () {
        var stack = this,
            arr   = [];

        registry[stack] = arr;
    }

    extend(Stack.prototype, {
        push (item) { registry[this].push(item); }
        pop () { return registry[this].pop(); }
    });

    return Stack;
}());

Nearly all bleeding-edge browsers support this, currently (minus the shorthand for methods).
But there are ES6 -> ES5 compilers out there (Traceur, for instance).
I don't think WeakMaps are supported in Traceur, as an ES5 implementation would require a lot of hoops, or a working Proxy, but a Map would work (assuming that you handled GC yourself).

This lends me to say that from a pragmatic standpoint, for a class as small as Stack you might as well just give each instance its own methods, if you really want to keep the array internal.

For other harmless, tiny, low-level classes, hiding data might be pointless, so all of it could be public.

For larger classes, or high-level classes, having accessors on instances with prototyped methods stays relatively clean; especially if you're using DI to feed in lower-level functionality, and the instance accessors are just bridging from the interface of the dependency, into the shape you need them to be, for your own interface.

Norguard
  • 26,167
  • 5
  • 41
  • 49
  • Wow, thanks for a very detailed answer. I'm not familiar with WeakMap so it'll take me a little time to digest it. By the way, I just updated my answer above and it seems to achieve (at least superficially) what I was hoping for. The `arr` is hidden and the `push` method is accessible both through which are accessible both through `s.push(x)` and `Stack.prototype.push.call(s, x)`. – Chris Middleton Oct 05 '14 at 03:32
  • 2
    WeakMap will be great. Basically, using `Map`, rather than `{ str : val }`, for traditional objects, any value can be used as the key `{ myObj : val }`, `myMap[myObj] === val`. A `WeakMap` does exactly the same thing, but when an object is GCed, it's also removed from the WeakMap (or, existing as a key/val doesn't preclude it from being GCed), whereas with a traditional Map (or an object/array), if the object is still contained in the map/set/obj/arr, it can't be cleaned. – Norguard Oct 05 '14 at 03:46
  • Solving without a `WeakMap` would basically look like `var stack = this, arr = [], id = UUID(); registry[id] = arr; this.id = id;`, and each method would just change to `registry[this.id].pop();` etc. But of course, that means that you need to find a way to tell the registry to remove the array, when you're done with the stack... ...like a `.destroy` method you manually call on the stack (lest it stay in memory), whereas the WeakMap would just stop caring about the stack/array instance if it didn't exist anymore. – Norguard Oct 05 '14 at 03:51
  • 1
    Also, your solution is rather clever - I've done plenty of those - but the reason you didn't want to have instance-based getters/setters was specifcally the memory-overhead (and technical-impurity) of doing so. Using binds, or closure-based solutions, per-instance will end up creating way more than accessors will (especially in Chrome, if they're all defined up-front in the constructor). Don't get me wrong, I try to be a functional guy (partial-application, thunks, collection-based atomic procedures, etc) versus trying to make all JS feel like C#/Java, but it comes with that particular cost. – Norguard Oct 05 '14 at 03:59
  • When `Proxy` is available, it will also pretty awesome at this kind of stuff I'd think, in combination with a `WeakMap` which actually would be perfect for this kind of thing. That's a great use for a `WeakMap`. I don't think a pollyfill for `WeakMap` is possible. It has to be automatically garbage collected. – Bjorn Oct 05 '14 at 04:24
  • @BjornTipling a generic polyfill wouldn't be. A context-specific implementation, where you manually nullify the links would be possible. So `Stack.prototype.kill = function () { delete registry[this.id]; }` is something you'd have to manually control, rather than just letting GC do it's thing (so it's prone to human error), but would serve the same purpose as WeakMap, and could just be ignored when WeakMap is widely available - something you made mention of, already. – Norguard Oct 05 '14 at 04:46