Mutant World

Monday, December 29, 2008

Visibility in JavaScript

The topic of visibility of members and functions in JavaScript has been figured out already, and there are good resources, among which one of the most important is Douglas Crockford's Private Members in JavaScript.

Here I just recap my experience for my own (and for anyone interested) future reference.

I am pretty paranoid in making all members and methods with the least possible visibility in my Java code. Before opening up a method as protected or public, one should always remind that most of the times, once it's opened up, you cannot close it because other code uses it.

JavaScript can have private members and functions, and I mostly use two idioms for this: the constructor function and the the object initializer.

The constructor function


I use the constructor function technique when I want to create few objects that behave the same (in Java you would say "few objects that belong to the same class", but JavaScript does not have classes).

var MyNS = {}; // An optional namespace
MyNS.Service = function(offset)
{
var _privateMember = offset;

function _privateFunction(value)
{
return _privateMember + value;
}

this.myFunction = function(value)
{
_privateMember = _privateFunction(value);
return _privateMember;
}
};

The common convention is that function names beginning with an upper case letter are constructor functions, in this case Service.

From the example above, private members are defined within the constructor function using the keyword var, private functions are just nested functions within the constructor function, and publicly accessible functions are defined with the this.<functionName> idiom.
The function myFunction in the example above is a privileged function, but for the sake of visibility it can be classified as public because anyone can invoke it.
From the example above, you see that myFunction can refer to private members and functions.

However, referring to publicly accessible functions from private functions is not possible:

// A constructor function without namespace; does not work
function Service2()
{
var _open;

function _clean()
{
if (_open) close(); // Does not work: ReferenceError
}

this.close = function()
{
...
}
}

It is not possible to call privileged functions from private functions. In my experience this is not a big problem, as it is possible to refactor function close() into a private function _close() and call the private version from both _clean() and close().

With a constructor function it is possible to create several objects that behave the same. In the example above you can create several service objects using the following syntax:

var s1 = new MyNS.Service(1);
var result1 = s1.myFunction(5); // returns 6

var s2 = new MyNS.Service(2);
var result2 = s2.myFunction(0); // returns 2

As you would expect, object s1 and s2 have different internal state and can be used independently.

The object initializer


I use the object initializer technique when I want to create a singleton object.
My preferred way of using the object initializer technique is via a function with immediate invocation, which allows to have private members and functions:

MyNS.EventHandler = function()
{
var _queue = [];

function _privateFunction(event)
{
...
}

return {
handle: function(event)
{
_privateFunction(event);
},
get size()
{
return _queue.length;
}
};
}();

Note the parenthesis at the end of the function definition, which perform the immediate invocation of the function.

Similarly to the constructor function technique, it is possible to define private members and functions, but the object initializer technique also allows (for non-crappy JavaScript interpreters) to use the getter and setter notation.
In the example above I used the getter notation to define a read-only property called size, which can be accessed like this:

var s = MyNS.EventHandler.size;

Using the getter notation is much nicer than having a getter function, and much better than having a public member (which would be not only readable but also writable).

Caveats


Both solutions outlined above have the problem that for each object created in those ways, the function definitions are also duplicated: each object will have its own copy of the functions, and this will result in more memory occupation. That's why I suggest the constructor function technique when you create few objects and the object initializer technique for singletons.

At first, I thought that there must have been a way to have private visibility for member and functions, but only one copy of the function definitions, very much like Java does: each Java object has its own copy of the members, but they all share the methods definitions.

Unfortunately I could not figure out how to do it, not even reading JavaScript library code such as Dojo or jQuery.

However, there is a different technique that it is possible to use to share the functions definitions, which I will call here "public members and prototype".

Public members and prototype


When you have to create lots of objects with members and functions, the best technique is to define the functions in the prototype, so that they will be shared by all objects, and have public members holding the state.
You have to give up on restricted visibility (members must be public) to save memory (only one copy of the functions is shared by all objects), because functions defined in the prototype can only (to my knowledge) refer to public members.

MyNS.Event = function(source)
{
this._source = source;
};
MyNS.Event.prototype = function()
{
var _privateStatic;

function _privateFunction()
{
}

return {
consume: function()
{
this._consumed = true;
},
get source()
{
return this._source;
}
};
}();

In this example, MyNS.Event is a constructor function that allows to create events via:

var source = window;
var event1 = new MyNS.Event(source);

The constructor function defines a public member called _source, which can be later referred from functions.
Also functions in the prototype can define public members (like _consumed in function consume()).

All function definitions are defined in the prototype. Note how the prototype object is returned via immediate function invocation, which again allows to have private functions and members as in previous techniques.

The interesting part comes when you define private members in the prototype, like _privateStatic.
These members can only be modified by functions defined in the prototype, which are shared by all objects. The result is that modifications made by one object to _privateStatic (via shared prototype functions) are reflected by all other objects created with the same constructor function. This behavior is the same behavior of static members in Java.

Instead, public members modified by functions defined in the prototype are attached to the object (not to the prototype), so that each different object has its own copy of the members, and therefore they can be modified independently:
 
function A(value)
{
this._field = value;
}
A.prototype = function()
{
var _staticField;
return {
staticGetter: function()
{
return _staticField;
},
staticSetter: function(value)
{
_staticField = value;
},
instanceGetter: function()
{
return this._field;
},
};
}();

var a1 = new A(1);
var a2 = new A(2);
a1.instanceGetter(); // returns 1
a1._field; // public field, returns 1
a1._field = 3;
a1.instanceGetter(); // returns 3
a2.instanceGetter(); // returns 2
a2.staticSetter('foo');
a1.staticGetter(); // returns 'foo'

I'd be interested in any solution that will allow private members with prototype-shared functions, if this is possible in JavaScript.

Labels:

1 Comments:

Post a Comment

<< Home