True Private Instance Variables (and Methods) in JavaScript
[Additon: Feb. 19, 2009]: I now recommend this approach instead.
[Addition: Jan. 12, 2008: Notice how, despite this approach being slightly cumbersome, it may be the most frequent need and recommended case for adding privacy--having private instance variables--as it is usually the data which must be kept private (and which varies per object) and which is often recommended to be always accessed through public methods which allow for potential future further changes in processing before handing over (or setting) the data.]
I came across this very clever method of giving true private instance methods to JavaScript (which did not require (publicly-accessible and constructed-on-each-instance) privileged methods). One thing his approach does not deal with, however, is private instance variables. (Note that we're not referring to private (static) variables which are the same across each instance and created in a closure; we're talking about genuine (private) instance variables.) Where he says in his code that "private stuff" can go, besides his private instance methods (which are really private static methods cleverly used as private instance methods by the call() method and which are handling public instance variables), only private static variables can go, and not private instance variables.
My approach that follows (which builds on his) has a few disadvantages of which I'm aware:
Its advantages:
NOTES
[Addition: Jan. 12, 2008: Notice how, despite this approach being slightly cumbersome, it may be the most frequent need and recommended case for adding privacy--having private instance variables--as it is usually the data which must be kept private (and which varies per object) and which is often recommended to be always accessed through public methods which allow for potential future further changes in processing before handing over (or setting) the data.]
I came across this very clever method of giving true private instance methods to JavaScript (which did not require (publicly-accessible and constructed-on-each-instance) privileged methods). One thing his approach does not deal with, however, is private instance variables. (Note that we're not referring to private (static) variables which are the same across each instance and created in a closure; we're talking about genuine (private) instance variables.) Where he says in his code that "private stuff" can go, besides his private instance methods (which are really private static methods cleverly used as private instance methods by the call() method and which are handling public instance variables), only private static variables can go, and not private instance variables.
My approach that follows (which builds on his) has a few disadvantages of which I'm aware:
- It requires some unsightly code in places (albeit just one pasteable line in a constructor and , for convenience, a pasteable line for each public method that wishes to use a private instance variable with shorthand syntax)
- It requires one public instance variable (an integer)--an incrementor for each instance (and a single private static iterator and private static container of instance objects across all instances) ; if this instance variable is altered (just as is usually the case with a privileged method being altered), the functioning of any derived object (including previously instantiated ones) can be compromised.1 However, if all consumers of the code are trusted, it should not be too difficult to enforce a no-external-access policy for any obj._$ (this is not an issue for front-end users unless your code uses insecure methods like eval() or the like).
Its advantages:
- There will only be one set of private instance methods produced across all instances (contrast this with potentially multiple (cumbersome and high memory) privileged functions added upon each instance in order to serve as a bridge to the prototype for the private instance variables in the constructor; our approach here doesn't need any such bridge (though it does produce an object for each instance to hold the private instance variables).
- Allows private variables and methods to be shared across constructor and public methods; the instance variables (and methods) are available everywhere in the class (constructor, and private and public instance methods) if accessed through __[this._$], or _. if a shortcut line is added to each method where private instance variables will be used)
- It operates transparently without need for any helper functions or prototype overloading, etc.
var SomeClass = function () {
var privStatic; // Not being used here
function privateStaticMethod () {} // Not being used here
// Private instance holder (will be used as such)
var _$ = 0; // Our private static incrementor (increment once per instantiation to distinguish objects)
var __ = []; // Each index on this array will be an object holding private instance variables, with each index corresponding to a unique id generated by each constructor and stored on each of its objects; see also note 2 below
// Private instance methods (to be used as such below)
function privInstMethod (instName) {
return __[this._$][instName];
}
function Constructor () {
this._$=_$; _$++; var _=__[this._$]={}; // Copy this line for each constructor
// Private instances code
_.privInst = 5;
}
Constructor.prototype.someMethod = function () {
var _=__[this._$]; // copy this line for each method which uses a private instance variable (to have a more convenient shortcut)
_.privInst++;
};
Constructor.prototype.getMethod = function () {
var _=__[this._$];
return _.privInst;
};
Constructor.prototype.anotherGetMethod = function () {
return privInstMethod.call(this, 'privInst'); // Call a private instance method as per Andrea Giammarchi's approach (i.e., treat a private static method as a private instance one); only the first argument is always required, the remaining arguments are the real arguments passed to the private instance method; we could also define, as he did, a shortcut, but that is defining an additional public method
};
return Constructor;
}();
var cl = new SomeClass();
cl.someMethod();
cl.someMethod();
alert(cl.getMethod()); // 7
var cl2 = new SomeClass();
// cl2._$=0; // The biggest down-side of our approach is that things can really get messed up if consumer code does something like this (or if an inheriting class similarly tampers with the variable), but at least there is only one such variable to mess up, making it less likely to happen (and it is not named in such a way as for most trusted consumers to be tempted to alter it), and does not provide access to the private members
cl2.someMethod();
alert(cl2.getMethod()); // 6
alert(cl.getMethod()); // still 7
alert(cl.anotherGetMethod()); // also 7
alert(cl2.anotherGetMethod()); // also 6
NOTES
- One could replace the first line in the constructor with this:
this._$= function get_$ (_$) { return function () {return _$}}(_$); _$++; var _=__[get_$()]={}; // Copy this line for each constructor
and then replace all other instances of "this._$" in the code with "this._$()" except for within the constructor, where you can use get_$().
This would add one privileged method to each instance (but only one--not one for each private variable) and be slightly more unattractive. An advantage of this would be that the function used internally within the constructor would be secure for previously instantiated objects (resetting obj._$() externally would not affect it), but it would only be secure within the constructor--not within its public methods (as would be the case for any privileged method). - The line "var __ = [];" might be divided into protected and private holders to allow inheriting classes to share access by passing in an object to this anonymous function (or create a global storage object for shared "protected" instance variables (or protected static ones)). I aim to discuss this possibility (as for a different approach to protected methods) in a future post.
2 Comments:
2 problems:
+ No protection: changing this._$ destroys all private member accesses
+ Memory leak: all private members are never garbage-collected
By Anonymous, at Saturday, 16 May, 2009
Sorry for the very delayed response, but I was blocked from blogspot until now.
Yes, I mentioned the vulnerability with this._$, though I'm not too familiar with garbage collection issues in JavaScript--I do understand that being stored in the static container means it will not be destroyed at least unless the container itself is destroyed, but I don't normally delete objects once created anyways.
As I added to the top of the post, I now recommend the following approach, though it is a bit more cumbersome and uses some function calls (though it is less unsightly and even less so if one allows the Relator class as a global): http://brettz9.blogspot.com/2009/02/true-private-instance-variables-in.html
While I prefer the other approach for true privacy, I still think my own approach provides a reasonable amount of sanity--JavaScript is hardly safe as it is anyways (at least until 2.0) from having prototypes overwritten, from deleting or redefining built-in methods, etc., so it seems to me that in most cases it is never a good idea to load scripts you don't control and then expect there will be no security conflicts. If you control it, or work with other developers, it's pretty easy to have discipline to enforce the "don't touch the this._$" rule.
Thanks for the feedback, and if you're still around, feel free to add more...
By Brett, at Saturday, 04 July, 2009
Post a Comment
<< Home