2

Some methods of one of my classes right now are public, but can access private variables (they are privileged). This is because they are created in the class constructor, so their closure has access to the object closure.

What I would like to avoid, though, is the memory and performance overhead of creating new privileged methods every time. So I want to share them.

Is there any way to put privileged methods into a prototype?

Example was requested:

function Person(age) {       // age is private
    this.grow = function() { // grow is now public, but can access private "age"
        age += 1;
    }
}

dan = new Person(10);
dan.grow();
dan.age;                    // undefined

This works, I have a public method "grow" that can access the private variable "age", but grow has to be recreated for each object.

The more performant way is:

function Person(age) { // age is private
    this.age = age;    // but this.age is public
}
Person.prototype.grow = function() {
    this.age += 1;
}

dan = new Person(10);
dan.grow();
dan.age;              // 11

This shares the "grow" method, but now age is public.

DanRedux
  • 9,119
  • 6
  • 23
  • 41
  • Is this a clever way of asking "can I have private variables in classes defined by prototype"? Not really, but the constructor can have access to an "object-store" that isn't visible to the outside world without generating closures every time the constructor is called. http://stackoverflow.com/a/483294/14357 Did you hit a performance issue that makes closures nonviable? – spender May 15 '14 at 01:18
  • I'm having trouble following your lingo (in a javascript-context), could you add an example of what you are trying to refactor? – GitaarLAB May 15 '14 at 01:23
  • Added example, @GitaarLAB Oh, spender, that actually looks like a good solution, but the guid has to be public now... So it's a 99% solution (and better than what I currently have). – DanRedux May 15 '14 at 01:33
  • Great example (great question), at least it prevents XY problem! So, effectively you want age to be private? – GitaarLAB May 15 '14 at 01:40
  • Why does it have to actually be private rather than just be private by convention? It is typical to "prefix" private variable with an `_`. – Aaron Dufour May 15 '14 at 01:42
  • @AaronDufour I know the standard is to use an underscore, but I was hoping for a solution. mpm: I'm starting to believe this might be true. So it can only be done with per-object functions then... There's no way to access the closure of an object? – DanRedux May 15 '14 at 01:48
  • This has been asked before a couple of times. Here is a pattern that implements protected, maybe with a little edit in the init function (a shared private returning the key) you can make it instance specific private: http://stackoverflow.com/questions/21799353/pseudo-classical-inheritance-with-privacy/21800194#21800194 – HMR May 15 '14 at 02:46

3 Answers3

1

You could do something like this (without ES6), though I don't consider it a good solution.

var Person = (function () {
    var id = 0,
        data = {},
        key = Math.random();

    function Person(age) {
        var thisId = id;

        data[id] = age;
        id += 1;

        this.getId = function(check) {
            if (check !== key) {
                return undefined;
            }

            return thisId;
        };
    }

    Person.prototype.grow = function () {
        var thisId = this.getId(key);

        data[thisId] += 1;
        console.log(data[thisId]);

        return this;
    };

    Person.prototype.destroy = function () {
        var thisId = this.getId(key);

        data[thisId] = null;
        delete data[thisId];
    };

    return Person;
}());

var dan = new Person(10);

dan.grow();
console.log(dan.age); // undefined
console.log(dan.getId()); // undefined

on jsFiddle

Added by @DanRedux:

function Person(age) {
 this.private = {age:age}; }
Person.prototype.grow = function() {
  this.private.age += 1; }

dan = new Person(10);
dan.grow();
dan.age; // undefined
dan.private.age; // 11
Xotic750
  • 22,914
  • 8
  • 57
  • 79
  • We could actually just do: `this.privates = {}`, and then put all the private variables in there. That way there's no "id" logic, only 1 public variable, and it gets GC'd. However, it's still not perfect, as it still exposes a variable. – DanRedux May 15 '14 at 01:59
  • Exactly, I thought you didn't want to expose the data? This only exposes an id. – Xotic750 May 15 '14 at 02:00
  • But what you have just added to my answer now exposes the data. – Xotic750 May 15 '14 at 02:03
  • Ohhh I see now. I get what you're trying to say, the data is no longer inside the object. Interesting solution. Of course, you could still get it by doing `data[dan.getId()]`, or even just printing out the `data` array. It looks like even `data` could be hidden inside the class (sort of like a static). Now if only we could hide getId. – DanRedux May 15 '14 at 02:07
  • Hmm. intriguing idea.. What about putting this code inside a closure.. `var Person=(function(){ /* snippet Xotic750 here */ return Person;})();` Hope you get the idea. that way Person is in the same private scope as `data`. Also, setting the `data[id]` to `null` is better then `delete` on most engines. – GitaarLAB May 15 '14 at 02:08
  • @GitaarLAB Yeah, that would hide the `data` array. The only missing ingredient is to hide the "getId()" method. – DanRedux May 15 '14 at 02:09
  • Sure you can put it in a closure to hide it even further away from any other code, but you are not going to get rid of `getId`, that is a required mechanism for this solution. And of course the other downside is that you have to do your own `destroying` of the data when no longer required. – Xotic750 May 15 '14 at 02:12
  • @DanRedux: yep, but you did get to use prototype (the performance-boost) whilst keeping your data private (to other scripts). – GitaarLAB May 15 '14 at 02:15
  • Very true, @GitaarLAB. This is the best solution, by far, as it only ever exposes a single variable. Is there a way to make that variable immutable from outside? – DanRedux May 15 '14 at 02:17
  • @Xotic750, if you agree with my addition in the comments, please add it to your answer for future readers of your answer. Then you've got my vote :) – GitaarLAB May 15 '14 at 02:25
  • the variable exposed is the function getId. An external script might overwrite anything it wants (if it can get to it), even your constructed Person constructor, at least your data is still protected. – GitaarLAB May 15 '14 at 02:29
  • @Xotic750: yep, that's the one *+1*. And thx for the idea. Looking at your idea gave me mine :) – GitaarLAB May 15 '14 at 02:31
  • If you are worried about other code being able to access the `id` of the instance then you could also add a key check, see update, and then when `getId` is called externally they will just get `undefined`. But you still have to expose the method `getId`. – Xotic750 May 15 '14 at 03:24
  • You could however mix in the idea from @AaditMShah answer and get rid of the exposed `getId` method. – Xotic750 May 15 '14 at 03:39
1

Yes, it is indeed possible. However it requires a little bit of trickery:

var createTree = require("functional-red-black-tree");

var Person = (function () {
    var tree = createTree(), id = 0;

    return function (age) {
        tree.insert(id, {
            age: age
        });

        this.id = id++;

        this.grow = grow;

        this.destroy = destroy;
    };

    function grow() {
        tree.get(this.id).age++;
    }

    function destroy() {
        tree.remove(this.id);
    }
}());

We use functional red black trees to efficiently insert, remove and get the private properties of an object in O(log n) time. Hence for example say you create 2251799813685248 instances of Person at a time. It will still only require 51 operations to insert, remove and get objects from the tree.

You can use it as follows:

var dan = new Person(10);
dan.grow();
dan.age;                  // undefined
dan.destroy();            // frees shared memory

However I wouldn't recommend this approach because:

  1. It unnecessarily complicates things.
  2. If you forget to call destroy then you will waste a lot of memory.
  3. Every "privileged" function has an additional overhead.
  4. Changes made to the secret object are not reflected on the private variables.

Instead I would recommend that you just use public properties for everything. There's really no good reason to use private properties at all. What are you scared of?


Edit: If alll you want is to prevent your private properties from being printed out via console.log then you can make them non-enumerable:

function Person(age) {
    Object.defineProperty(this, "age", {
        enumerable: false,
        writable: true,
        value: age
    });
}

Person.prototype.grow = function () {
    this.age++;
};

Now the age property (although public) will not appear in for in loops or via console.log. In short you get the best of both worlds.

As I said in the comments, there's absolutely no need to use the shared privileged method hack. Simply make all your variables public and non-enumerable.

In addition prefix them with an underscore to indicate that they should not be tampered with. All good JavaScript programmers use this convention.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • I don't like that the privates show up when I do console.log, mainly. However, it's also that I'd like to protect certain variables from being changed. – DanRedux May 15 '14 at 03:12
  • This of course does not answer the question "Is there any way to put privileged methods into a prototype?" But it does lead to the possibility of removing the need of `getId` by doing the `keys.push(this);` But reduces the number or reords available because of the use of an array, and the need for ES5 methods (which can be shimmed). – Xotic750 May 15 '14 at 03:35
  • To save a future reader a night of googling this statement: `But reduces the number or records available because of the use of an array`. The specification states that 'real' array indexes (in javascript methodology) *must* be positive integers which *must* be passed through an (engine-internal) 'u32int' function. Hence 'real' array-indexes are limited to 32 bits (unsigned) = `2^32 - 1` = `4 294 967 295 (dec)`. However, anything outside this range becomes a property of the array (using the 'array-like' interface `[]`), unless you `unshift`/`push` etc. in which case it fails. – GitaarLAB May 15 '14 at 13:34
  • @DanRedux: You said that you would like certain variables from being changed. Who will change those variables? I am certain that you would not change those variables yourself. Are you scared that somebody would accidentally change your variables and result in the program being in a bad state? In that case it's that other person's problem, not yours. You could always prefix public variables that should not be tampered with, with an underscore to state your intent. However resorting to hacks like these is rarely a good idea. Code should be simple. Don't complicate it more than it needs to be. =) – Aadit M Shah May 15 '14 at 15:36
  • It's not so much that people can change the variables, more than things can access them needlessly. A simple example is console.log(), I don't want my _private variables to be shown there. – DanRedux May 15 '14 at 21:47
  • @Xotic750 I changed my answer to use red black trees instead. The implementation should now be much faster. – Aadit M Shah May 16 '14 at 02:24
  • @GitaarLAB I changed my answer to use red black trees instead. The implementation should now be much faster. – Aadit M Shah May 16 '14 at 02:25
0

Well, as you have said, privileged methods are created by placing them in the constructor scope. By that definition, they cannot be shared amongst instances; otherwise they wouldn't be able to access the instance-specific scope.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375