My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Friday, April 13, 2007

JavaScript private Python style properties - yet another experiment

JavaScript constructors (call them classses if You prefere) have some problem with private parameters or private methods.

To use a private parameter You should simply use a variable without this as scope prefix
function MyConstructor(){
this.getPrivate = function(){
return private;
};
var private = "something";
};

It's simple and it works perfectly.
Any external scope could get directly private variable and You can know its value only calling getPrivate method.

var obj = new MyConstructor;
alert(obj.getPrivate());

The cool thing is that if You change externally returned value it will not be changed inside object too but please remember that if You change them and it's a generic object or an array, these changes will be valid inside object scope too (object and arrays are sent by reference, You need to clone or copy them before to return them externally).

Well, this is just a private parameters introduction and this is not useful if You write only using prototype or Class.create style, everything, in these cases, will be public (remember that).

The other "trick" to have a private method is to use internal scope (nested) constructor functions.
function MyConstructor(){

this.public = "something";

this.setPublicValue = function(what){
this.public = what;
setPrivateValue(what + private);
};

function setPrivateValue(what){
private = what;
};

var private = "something";
};

Well done, we have a public method and a private one ... but what should we do to have correct scope inside private methods too?
function MyConstructor(){

this.public = "something";

this.setPublicValue = function(what){
setValue("no " + what);
};

function setValue(what){
this.public = what;
};
};

Above code will not work because when You call a function without a dedicated scope, using for example apply or call, this will be just the global window object (super object).
We need to use call or apply, and problem is solved
function MyConstructor(){

this.public = "something";

this.setPublicValue = function(what){
setValue.call(this, "yes " + what);
};

function setValue(what){
this.public = what;
};
};
var o = new MyConstructor;
alert(o.public);
o.setPublicValue("now I can set them");
alert(o.public);



Well ... and what about choosed topic?
These examples shows that JavaScript constructors should have both private parameters and methods, but sometimes I think about other languages style and this is my experiment.
function MyConstructor(a,b,c){

this.getVar = function(){
return this.__privateVar;
};

this.setVar = function(){
this.__privateMethod("private var setted by a private method");
};

this.__privateVar = "private var";

this.__privateMethod = function(arg){
this.__privateVar = [arg, [a,b,c]].join("\n");
};
};

MyConstructor = MyConstructor.toPrivate();

var asd = new MyConstructor(1, {}, false);
alert(asd.__privateVar); // undefined !!!
alert(asd.getVar()); // private var
asd.setVar();
alert(asd.getVar());
// private var setted by a private method
// 1, [object Object], false

"Public" parameters and methods that use double underscore "__" as prefix are privates!
This means that You can just write without apply or call problems simply calling private variables or functions using this as scope prefix and "__" before real name.
You could even call two vars or methods in the same way, just add __ before private one.


How can we do it?
Using this stupid prototype:
Function.prototype.toPrivate = function(){
var c = "".concat(this),
o = new this,
v = [],
k;
for(k in o) {
if(k.substr(0,2) === "__") {
v.push("var ".concat(k, "=o.", k, ";"));
c = c.replace(new RegExp("\\bthis\\s*\\.\\s*(".concat(k, ")\\b"), "g"), "$1");
if(o[k] && o[k].constructor === Function)
c = c.replace(/\}\s*$/, k.concat("=(function(s,m){return function(){return m.apply(s,arguments)}})(this,", k, ");", "}"));
}
};
eval(v.join("").concat("c=", c));
c.prototype = this.prototype;
return c;
};

Seems horrible? Yes, it is :D

What about this proto limits?
Well, the first limit is that I suggest You to write private methods or parameters correctly, as I showed at the top of this post, because a code that use eval and works on constructor toString native or modified method isn't a cool way to solve private scope.
The second limit is that You need to remember to call toPrivate prototype.
There are probably other limits but as I wrote, this is just an experiment, I hope funny for your "JS dynamic investigation", bye :)

3 comments:

Anonymous said...

Do you like python too huh? Recently I emulate python's lambda to javascript:
http://www.riiv.net/2007/04/21/pythons-lambda-in-javascript/

Anonymous said...

There is a beeter method to get Listing 4 to work without apply() or call(). Just add this line to MyConstructor:

var _this = this;

and then use _this in "private" functions to acceess original object:

function setValue(what){
_this.public = what;
};

Andrea Giammarchi said...

It's obvious and I usually use var self = this ... however, it doesn't work during instance construction so You need some work around but this post talks about an automatic example ... anyone could send a self argument (python like) or use a fake this as reference ...

Finally I think apply or call aren't a bad solution, this is this ... what else (on private functions too) ;)