Wednesday, October 22, 2008

a new Relator object plus unshared private variables

This is a Robert Nyman's post dedicated reply, about JavaScript inheritance and alternatives and private variables.

First of all, I would like to say thanks to Robert for both interesting articles He is writing about JS inheritance, and a link to a personal comment which aim was to bring there my "old" documentation about classical JavaScript inheritance and usage of prototype, closures, and public, privileged, or private, scope when we create a constructor.

About


Last Rob's post talk about private variables, describing them as shared, if present outside the constructor, and valid only for singleton instances.
This is true, and could cause a lot of headache if we are not truly understanding closures and prototype shared methods behaviour, but there is a way to use this peculiarity about shared private variables to create dedicated private variables.

Before I will write about it, let's look into a generic shared private variable example:

Click = function(){
// closure for private methods / variables

// private shared variable
var _total = 0;

// returned constructor + prototype
function Click(){};
Click.prototype.add = function(){
_total++;
};
Click.prototype.getTotal = function(){
return _total;
};
return Click
}();

var left = new Click,
right = new Click;

left.add();
alert(right.getTotal()); // 1

Above example shows that if a variable is created outside the constructor and prototype shared methods use that variable, every call to one of those method from a generic instance will modify that single private variable, created once inside that closure. To obtain a private variable we need privileged methods:

Click = function(){ // privileged methods
var _total = 0;
this.add = function(){
_total++;
};
this.getTotal = function(){
return _total;
};
};

var left = new Click,
right = new Click;

left.add();
alert(right.getTotal()); // 0

As discussed before in my doc, in robert's posts, and everywhere else in the web, privileged methods create many functions for each instance, and with big projects this is not good for both memory and CPU usage.

new Relator object and unshared private variables


The Relator is an object capable to relate a generic variable with a clear object, without modifying the original variable.
The classic example is this:

var myNum = 123;
Relator.set(myNum).description = "I am number 123";
alert(myNum.description); // undefined
alert(Relator.get(myNum).description); // "I am number 123"

Since with Relator it is possible to create a unique relation between a generic object and a private Object, it was natural for me to think that a private variable could be a Relator like object using this as unique relationship.
The new version of my Relator still respect the precedent API, being a Relator itself, but is now able to return a new object Relator like that could be used, as example, inside a closure.

// new Relator can create a Relator like object with method "$"
PrivateRelator = function(Relator){
// Relator argument is a new Relator like object
// present only in this scope
return {
get:function(what){
var value = Relator.get(what);
return value && value.value;
},
set:function(what, value){
Relator.set(what).value = value;
}
}
}(Relator.$()); // create a new Relator

Relator.set(window).value = "Hello World";
Relator.get(window).value; // Hello world
PrivateRelator.get(window); // undefined
PrivateRelator.set(window, "Hello Private World");
PrivateRelator.get(window); // Hello Private World
Relator.get(window).value; // Hello World

The good part of Relator is that the stored variable, if it is an object, is stored by reference, and we can assign more than a private unique relationship between a single object and our extra informations.
The same pattern could be easily re-adapted to create a shared private variables:

Person = function(){

// private methods
function _getName(){
return _private.get(this).name
};
function _setName(name){
_private.get(this).name = name;
};

// private variable, shared by every function in this scope
var _private = Relator.$();

// constructor + prototype
function Person(){
// bridge to _private shared variable
_private.set(this);
};
Person.prototype.getName = function(){
return _getName.call(this);
};
Person.prototype.setName = function(name){
_setName.call(this, name);
};
return Person;

}();

// extended constructor
function Enhanced(){
// necessary to save
// this instance into _private variable
// accessible only via Person scope
Person.call(this);
};
(function(){ // quick runtime extend
var callee = arguments.callee;
if(this instanceof callee)return;
callee.prototype = Person.prototype;
Enhanced.prototype = new callee;
})();


var me = new Person(),
rob = new Enhanced();

me.setName("Andrea");
rob.setName("Robert");

alert([me.getName(), rob.getName()]);
// Andrea,Robert


Conclusion


Sometime a limitation could be easily managed to obtain desired result.
In this case, thanks to JavaScript closures and its prototype nature, the shared variable become a sort of bridge to obtain private variables for each instance.
It is important to understand that even if we extend the base constructor, every new instance will still persist into original _private variable, inside the Person scope, unless we do not specify a different one, redefining every method that use that variable as well:

(function(_private){
Enhanced = function(){
_private.set(this);
};
(function(){ // quick runtime extend
var callee = arguments.callee;
if(this instanceof callee)return;
callee.prototype = Person.prototype;
Enhanced.prototype = new callee;
})();
Enhanced.prototype.getName = function(){
return _private.get(this).name
};
Enhanced.prototype.setName = function(name){
_private.get(this).name = name;
};
})(Relator.$());


See you soon ;)

No comments:

Post a Comment