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

Friday, March 21, 2008

How to inject protected methods in JavaScript - Part II

As a performances maniac, I've found another way to inject protected methods, or something similar, without unnecessary overload of each public method.

Of course, precedent way is definitively more clear, linear, but if you have a constructor.prototype with a lot of private methods, the overload process will be a wall in front of method execution speed.

Each time you will use a public method, you have to set, and unset, every protected method, and at the same time, as explained in my precedent post, you cannot send the object outside during public method execution, because it will expose every method, protected included.

This new proposal is based on a Function like strategy, applied to instances: apply, and call. Please have a look at this prototype function:

function prototype(constructor, public, protected){
// webreflection - mit style
if(protected){
public.apply = function(callback, arguments){
return (callback.charAt(0) === "_" ? protected : this)[callback].apply(this, arguments);
};
public.call = function(callback){
return this.apply.call(this, callback, Array.prototype.slice.call(arguments, 1));
};
};
public.constructor = constructor;
constructor.prototype = public;
return public;
};

A usage example should be this one:

function MyMath(value){
this.value = value;
};
prototype(
MyMath,
{doStuff:function(){
return this.value * this.value;
}},
{_doStuff:function(){
return this.value + this.value;
}}
);

The first sent object will be the prototype of MyMath constructor, while the second one will contain protected methods.

About performances


As you can see in the simple prototype function, there's no override and no overhead for both public and protected methods. This means that you can use a public method directly and without problems:

var num = new MyMath(3);
num.doStuff(); // direct, fast as regular prototype


About protected methods


These simply "does not exists" :)

var num = new MyMath(3);
num._doStuff(); // error

Protected methods are not directly accessible even from instance itself. This means that these methods are safe and not mutable, if used object is created in a private scope, or runtime as showed before.

How to use protected methods



var num = new MyMath(3);
alert(num.call("_doStuff")); // 6

Apply and call, in this case, are used with instance to call a method that, if contain an underscore as first prefix char, will be one from protected prototype, otherwise will be one of the public prototype. Sounds weird?

// full example
function MyMath(value){
this.value = value;
};
prototype(
MyMath,
{doStuff:function(){
return this.value * this.value;
}},
{_doStuff:function(){
return this.value + this.value;
}}
);

var num = new MyMath(3);
alert([
num.doStuff(), // 9
num.call("doStuff"), // 9
num.call("_doStuff") // 6
].join("\n"));

num._doStuff; // undefined


Why do I call them protected


If everyone can call a method, it does not make sense to call them protected.
But this time, the meaning is not the same of classical inheritance, but it is more close to the word itself: proteced.

The main goal is to have a not common or usual way to call methods, specially for libraries developers.
Every API could be based on public methods, while authors could choose to call a protected method when their need them.
This is basically a transparent layer between the developer and the final user: who will use num.call("doStuff") when you can do this: num.doStuff() ?

I know I am not so good to explain my ideas, so I hope someone will find this useful. Have a nice easter ;)

2 comments:

kangax said...

Very nice.
Is it possible to simulate private methods in such way that they are visible from within instance methods, yet completely inaccessible after object instantiation?

Andrea Giammarchi said...

Of course it is possible. Just add a closure in the prototype.


function A(value){
this.value = value;
};

A.prototype = new function(){

/* private stuff */
function _doStuff(){
return this.value * this.value;
};

/* public stuff */
this.constructor = A;
this.doStuff = function(){
return _doStuff.apply(this, arguments);
};
};

var a = new A(3);
alert(a.doStuff()); // 9