Brett's Blog
Edit     New

Thursday, February 19, 2009

True Private Instance Variables in JavaScript Without Any Public Properties

As a follow-up to my article on True Private Instance Variables, I've since discovered Andrea Giammarchi's fully private technique which also works across the constructor and prototype methods and has the benefits of not creating separate privileged functions on each instantiation, but which does not create any public properties at all.

His latest code is available here.

Here's my own slightly cleaned up version (basically just fixed/added some brackets, semi-colons, and usage notes):


// Used for giving privacy
var Relator = function () {
// Code from http://www.devpro.it/code/192.html
// Relator explained at http://webreflection.blogspot.com/2008/07/javascript-relator-object-aka.html
// Its use as privacy technique described at http://webreflection.blogspot.com/2008/10/new-relator-object-plus-unshared.html
// 1) At top of closure, put: var _private = Relator.$();
// 2) In constructor, put: _private.set(this);var _ = _private.get(this);
// 3) At top of each prototype method, put: var _ = _private.get(this);
// 4) Use like: _.privateVar = 5;
//
// ******************************************
// (c) Andrea Giammarchi - Mit Style License
// ******************************************
// Relator.set(123).description = "Number 123";
// Relator.get(123).description; // Number 123
// Relator.len(); // 1
// Relator.del(123).len(); // 0
// Relator.set("abc"); // object Object
// Relator.len(); // 1
// var anotherRelator = Relator.$();
// anotherRelator.len(); // 0
// Relator.get("abc"); // object Object
// anotherRelator.get("abc"); // undefined
// ******************************************
function indexOf (value) {
for (var i = 0, length=this.length; i < length; i++) {
if (this[i] === value) {
return i;
}
}
return -1;
}
function Relator () {
var Stack = [], Array = [];
if (!Stack.indexOf) {
Stack.indexOf = indexOf;
}
return {
// create a new relator
$ : function () {
return Relator();
},
// remove a referenced object
del: function (value) {
var i = Stack.indexOf(value);
if (~i) {
Stack.splice(i, 1);
Array.splice(i, 1);
}
return this;
},
// get a referenced object, if any, or undefined
get: function (value) {
return Array[Stack.indexOf(value)];
},
// return total number of referenced objects
len: function () {
return Stack.length;
},
// set a reference for a generic object, if it is not present
set: function (value) {
var i = Stack.indexOf(value);
return ~i ? Array[i] : Array[Stack.push(value) - 1] = {};
}
};
}
return Relator();
}();


What follows is an example of a closure creating a class and using the Relator for private variables. Note that if you don't even want to introduce the above Relator object/class into the global namespace (say if you are distributing your class as part of a library), you can just add it at within the closure creating your class at the very top of the closure (instead of just calling it, as I do below).

Note that in my example, I put the boring repetitive code at the very top of each function, right after the bracket, as I find this lets one focus on the main logic of one's class, without getting distracted by the private instance variable set-up code (and also makes it easy for me to find the code for pasting into other methods or classes).

I've also stuffed the code with comments to demonstrate where all of the other types of common variable and method types could go



var MyClass = (function () {

// Put Relator class here if you don't want it to be public (but it is VERY useful to have available without putting it inside of each class)

var _private = Relator.$(); // At top of each closure where private variables are needed, put this for set up (in combination with set() call at top of constructor below

// Note that you can also put private static variables and methods here, or private instance methods (which are really private static methods to which 'this' is passed in methods below; e.g., myPrivMethod.call(this, arg1, arg2);)

function Constructor () { var _ = _private.set(this); // At top of each constructor
_.privateInstanceVar = 3; // You can now define and use private variables in the constructor (just put the desired variable name after "_."
// _.privateInstanceMethod = function () {}; // You could even make private instance methods dynamically which could be available throughout the methods
// this.publicInstanceVar = ... ; // You can use public instance variables
// this.privilegedMethod = ... ; // If you actually want a privileged method
}
Constructor.prototype.somePublicMethod = function () {var _ = _private.get(this); // At top of each prototype method needing to use private variables, so you can define and use private variables here too
_.anotherPrivateVar = 5;
alert('My first private variable is '+_.privateInstanceVar+' and my new one is '+_.anotherPrivateVar);
};
// Constructor.prototype.someVariable = 5; // Default public variables (which can be overridden by instance or changed for all instances)
// Constructor.publicStaticMethod = function () {}; // You can put public static methods here
// Constructor.publicStaticVariable = 5; // You can put public static variables here
// var consts= {'myconst':5}; Constructor.getConstant = function (name) {return consts[name];}; // Define public static method to get public static constants; MyClass.getConstant('myconst');
// Constructor.__defineGetter__('myconst', function(){return 5;}); // or, to get Class constants in format MyClass.myconst (Mozilla only)
return Constructor;

})();

var myObj = new MyClass();
myObj.somePublicMethod(); // My first private variable is 3 and my new one is 5
alert(myObj.privateInstanceVar); // undefined


The only advantage my earlier version offers (if you can call it that), is that, you don't need the Relator class, and it doesn't involve function calls. However, the fact that you don't need ANY public variables (even just counter ones) to get private variables makes this approach highly worthwhile.

Finally, JavaScript now can have genuine private instance variables (at least a valid equivalent) without too much pain. [Additional note: With extending the class, you can do so if you're willing to call the parent constructor (you already have to do so at least once when creating the prototype for classical inheritance).]

1 Comments:

  • Hey Brett, I recently wrote an article on a very similar thing. I've been using this way to create instance variables for months now and only enjoy it more and more.

    http://strd6.com/2010/09/javascript-instance-variables-do-exist/

    I think you might be interested in checking it out as it requires a little less code overhead and provides a friendly `I.` prefix for accessing instance variables. It also allows easy use of modules and trait based inheritance, which turns out to be another big plus.

    I'm always interested in this stuff so I'd love to hear your thoughts on the trade-offs involved in each technique.

    By Anonymous Daniel X. Moore, at Thursday, 16 September, 2010  

Post a Comment

<< Home


Google
 
Brett's Blog Web