Monday, April 7, 2008

Natural JavaScript private methods

I've never seen this technique yet, but basically it allows us to create private methods, without privileged, and in an way that does not allow subclasses to inherit them ... does it sound interesting? ;)

As we can read in my JavaScript Prototypal Inheritance for Classical Emulation documentation, there is a way to easily create private methods without usage of privileged.

The advantage of this way is, as explained in my doc, is that JavaScript interpreter does not have to create many functions for each declared instance.

Here there is a basic example:


// our constructor
function Person(name, age){
this.name = name;
this.age = age;
};

// prototype assignment
Person.prototype = (function(){

// we have a scope for private stuff
// created once and not for every instance
function toString(){
return this.name + " is " + this.age;
};

// create the prototype and return them
return {

// never forget the constructor ...
constructor:Person,

// "magic" toString method
toString:function(){

// call private toString method
return toString.call(this);
}
};
})();

// example
alert(
new Person("Andrea", 29)
); // Andrea is 29

Function toString will be shared by prototype with every created instance for the simple reason that every instance will inherit prototype.toString method and, at the same time, it points to private scope where toString function has been defined.
Is everything ok? Perfect, because we have to comprehend quite perfectly above example to understand what we are going to do right now ( and if you do not understand, read my doc to know more :P )

What we have to do each time is to remember that when we need a private method, with our instance injected scope, we have to write in an unnatural way.

What I mean is that if we usually use the underscore prefix to define our virtually protected methods, why couldn't we use them to define a function for private stuff only?

// our constructor
function Person(name, age){
this.name = name;
this.age = age;
};

// prototype assignment
Person.prototype = (function(){

// private stuff
function toString(){
return this.name + " is " + this.age;
};

// prototype
return {

constructor:Person,

toString:function(){

// call private toString method
// in a more natural way
return this._(toString)();
},

// define private methods dedicated one
_:function(callback){

// instance referer
var self = this;

// callback that will be used
return function(){
return callback.apply(self, arguments);
};
}
};
})();

// example
alert(
new Person("Andrea", 29)
); // Andrea is 29

The difference is basically in this line of code:


// instead of this way
return toString.call(this);

// we have this one
return this._(toString)();



Please do not forget that these methods are private, so there is no way to use them in subclasses, if those are created in an external or different closure, and that is exactly an expected behaviour (these functions are private).
But at the same time, if a subclass call an inherited method that use inside the parent prototype the private underscore, it will work perfectly.


// basic extend function
function extend(B, A){
function I(){};
I.prototype = A.prototype;
B.prototype = new I;
B.prototype.constructor = B;
B.prototype.parent = A;
};

// same stuff ...
function Person(name, age){
this.name = name;
this.age = age;
};
Person.prototype = (function(){
function toString(){
return this.name + " is " + this.age;
};
return {
constructor:Person,
toString:function(){
return this._(toString)();
},
_:function(callback){
var self = this;
return function(){
return callback.apply(self, arguments);
};
}
};
})();

// subclass
function Employee(company, name, age){
this.parent.call(this, name, age);
this.company = company;
};

extend(Employee, Person);

Employee.prototype.getFullDetails = function(){
// toString has been inherited from Person
// and it uses inside the private method
return this.toString() + " and works in " + this.company;
};

var other = new Employee("Mega Ltd", "Daniele", 26);
alert(
other.getFullDetails()
);

Finally, what we can do with this method, is to redefine them to allow us to overwrite private functions and/or use them without problems:

// above stuff + subclass
function Employee(company, name, age){
this.parent.call(this, name, age);
this.company = company;
};

extend(Employee, Person);

// extend prototype and return them
Employee.prototype = (function(proto){

function toString(){
return this.company;
};

proto.toString = function(){
return this.parent.prototype.toString.call(this) + " and works in " + this._(toString)();
};

proto._ = function(callback){
var self = this;
return function(){
return callback.apply(self, arguments);
};
};

return proto;
})(Employee.prototype);

alert(new Employee("Mega Ltd", "Daniele", 26));
// Daniele is 26 and works for Mega Ltd


That's it :)

No comments:

Post a Comment