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

Friday, January 12, 2007

prototype, singleton, what else ?

A lot or JS developers use prototype style to write (extends) classes (functions) or singleton to have a private scope.

I often use different ways to create my classes (function) and I use prototype only to extend and not to create.

These are some examples:

/** prototype style */
// class Blog
function Blog(){};

// prototype to extend Blog
Blog.prototype = {
getName:function(){
return this.name;
},
setName:function(name){
this.name = name;
},
name:""
};


// how to use Blog class
var myblog = new Blog;
// new Blog() with parenthesis is not useful.
// full prototype style "doesn't accept"
// parameters on constructor
// (or better, it accepts but doesn't use them)

myblog.setName("WebReflection");
// setName is absolutely necessary to set public
// name parameter

/** prototype style */



/** singleton style */
function Blog(name){
return {
getName:function(){
return name;
// name has a global scope
// in this object, private
// for every other external scripts
}
};
};

// how to use this Blog function
var myblog = Blog("WebReflection");

// Singleton style should accept one or more
// arguments on fake constractor.
// myblog is an instanceof Object and
// is not a Blog class (I've not used new to set this var)

The first important difference is that with Singleton you don't need to create setName because a blog name shouldn't be modified from other scripts (in this example).
Then why You should declare public parameter (classVar.name) or public methods (classVar.setName) when a property shouldn't be changable ?

This is a common full prototype style problem, everything is public and everything should be modified from other scripts.

In the other hand, with above Singleton example You don't create an instance of Blog, just recieve everytime a new Object.

With prototype = {something} Your varible will be an instance of className but constructor will be an Object while if You declare every method "manually" variable constructor will be exactly the original function.

function Person1(){};
Person1.prototype = {
getClassName:function(){return "Person1"},
isPerson1:function(){return this instanceof Person1}
};
var p1 = new Person1;
document.write([
p1.getClassName(),
p1.isPerson1(),
p1.constructor,
p1.constructor === Person1
] + "
");
// Person1,true,function Object() { [native code] },false


function Person2(){};
Person2.prototype.getClassName = function(){return "Person1"};
Person2.prototype.isPerson2 = function(){return this instanceof Person2};
var p2 = new Person2;
document.write([
p2.getClassName(),
p2.isPerson2(),
p2.constructor,
p2.constructor === Person2
]);
// Person1,true,function Person2() { },true

For many developers it is a feature, because they think that native code is faster than function eveluation (that should be performed every time) ... however, I've never seen performance differences from prototype style and other styles.
Think that func by func prototype doesn't work as single object too, then I suppose there are a lot of scripts that clones prototype (or extends them) using a for in loop and that these scripts doesn't perform better (if it's true) than others.

This is another way, not native, to have both instanceof class and private scope on object You should do something like that:

function Blog(name){
this.getName = function(){
return name;
// as Singleton, name has
// a global scope inside this
// instance, private for
// every other script
};
};

// You could set private methods too ...
function Blog(name){

// private method, global scope
// inside this instance
function setName(){
selfname = arguments[0] || name;
};

// private variable
var selfname;

this.getName = function(){
setName(name);
return selfname;
};
};

var myblog = new Blog("WebReflection");
// You have an instanceof class Blog with
// a private internal scope and an external one


These kind of class instances are not possible to create with full prototype style and in this case evey instance will not be modified if other scripts changes Blog.prototype.getName with a new function.

function Blog(name){
function setName(){selfname = arguments[0] || name};
var selfname;
this.getName = function(){
setName(name);
return selfname;
};
};
Blog.prototype.getName = function(){
return "prototype doesn't change anything";
};

var myblog = new Blog("WebReflection");
document.write(myblog.getName()); // WebReflection

So this way should be thought as "more secure" than others because created instance will not be affected by prototype changes on constructor or on global Object, however, Singleton should be secure too.


function Blog(){
return {getName:function(){return "WebReflection"}}
};
Blog.prototype.getName = function(){
return "override";
};
Object.prototype.getName = function(){
return "override";
};

var myblog = new Blog;
document.write(myblog.getName()); // WebReflection

// if You don't use new ..
var myblog2 = Blog;
document.write(myblog2.getName()); // override

As I've said, the bad thing of singleton is that resulting variable will not be an instance of used className with or without new before declaration.


Last way, if You need a single object instance in your script, You should use this way too.

Blog = new function(){

// private method
function setName(){
selfname = arguments[0] || name;
};

// private variable
var selfname;

// public method
this.getName = function(){
return selfname;
};
this.setName = function(name){
setName(name);
};
};

Blog.setName("WebReflection");
document.write(Blog.getName());

// what kind of instance ? ... it's an "anonymous secret"
document.write(Blog instanceof Function); // false
document.write(Blog instanceof Blog.constructor);// true

This example is something like Singleton without the possibility to send arguments on constructor (and this one will not be a native Object code).
So this method has a "secret" constructor but You could extend them using Blog.constructor.prototype or extend another class using new Blog.constructor.
This example should be useful when You need a single instance of an anonymous function (a bit harder to be modified from other scripts) but remember that
to create a new instance You need GlobalObj.constructor and that other scripts should change a GlobalObj.construtor with prototype.

Now, this post is just to show different ways to create an "instance of something" ... but at this point I think that:

  1. if native code constructr is really faster to execute, Singleton should be the better choice (private scope too)
  2. if speed is important (and native code is really faster) but We don't need a private scope and We need to change every method/property of every className instance, prototype style is the better choice because We can know if a var is instance of a class and not only a generic instanceof Object.
  3. if extreme speed is not a problem (or native code doesn't perform faster), the best solution should be the simple function, to know both constructor and instanceof ... but We can't change every var in a single line.
  4. if we need a single anonymous instance, we could use the last example.

The third point limit, the possibility to change every instance of a class, should be solved with a simple Object proto

Object.prototype.syncronize = function(){
for(var key in this.constructor.prototype)
this[key] = this.constructor.prototype[key];
};

function Blog(name){
function setName(){
selfname = arguments[0] || name;
};
var selfname;
this.getName = function(){
setName(name);
return selfname;
};
};

var myblog = new Blog("WebReflection");
document.write([
myblog.constructor === Blog,
myblog instanceof Blog,
myblog.getName()
]);
// true,true,WebReflection


Blog.prototype.getName = function(){
return "override";
};

document.write([
myblog.constructor === Blog,
myblog instanceof Blog,
myblog.getName()
]);
// true,true,WebReflection


myblog.syncronize();

document.write([
myblog.constructor === Blog,
myblog instanceof Blog,
myblog.getName()
]);
// true,true,override


So, wich way do You like and Why ?

3 comments:

Andrea Giammarchi said...

Oooops, I forget one thing ... if someone think that native code is the key to choose prototype, just look at this code:

function Blog(){
this.func = function(){}
};
Blog.prototype = {};

native code, Object constructor ... simple ? :D

Mega said...

Very interesting!

kentaromiura said...

Very nice thought.

I think that if someone use the 'Singleton' way, he know exactly what he is doing..

so the use of istanceof is generally avoidable,
unless you are writing some very generic function..

in this case, you simply can add a 'constructorname' property,
and check for that