When writing JavaScript, most people don’t think about member access. Every member in an object is essentially a public variable. This is because there isn’t support for declaring variable modifiers (ie. public, protected, private) in the language. For most simple developments, this shouldn’t be a problem, and I never really thought too hard about it. But now that I’m knee deep in a big JavaScript project (and interesting new JS projects popping up like node.js) putting more consideration into encapsulation is a good idea.
According to Douglas Crockford it’s possible to create private variables and methods, and privileged methods (public methods with access to private variables and methods). Crockford’s article also gives us some patterns to follow which we can easily follow.
I was curious to see how this might look like in my preferred JS framework, Prototype.js. It didn’t take long before I concluded that you can’t use private variables with Class.create().
Here’s a rather useless class:
var AssetRequest = Class.create({
initialize: function(_id, _url) {
this.id = _id;
this.url = _url;
this.loaded = false;
this.content = null;
this.request = null;
},
load: function() {
if (!this.loaded) {
this.request = new Ajax.Request(this.url, {
onSuccess: function(transport) {
this.loaded = true;
this.content = transport.responseText;
Event.fire(document, 'assetrequest:loaded', this);
},
onFailure: function() {
Event.fire(document, 'assetrequest:failed', this);
}
});
}
}
});
The idea here is that you will create an AssetRequest object for an asset, say a JSON file. As you can imagine, all the members are editable. You can do something really dumb like this:
var request = new AssetRequest('req1', '/assets/asset-1.js');
request.loaded = true;
if (request.loaded) {
alert(request.id + ' is loaded');
}
If you count on request.loaded to tell you if an asset has been loaded, it’s not reliable. So obviously we want to make this a private variable set it’s value internally, and create a privileged method that will provide access to its value. But you’ll see that this is actually not possible if we construct AssetRequest with Prototype’s Class.create.
It’s tempting to write:
var AssetRequest = Class.create((function() {
var id,
url,
loaded = false,
content = null,
request = null;
return {
initialize: function(_id, _url) {
id = _id;
url = _url;
},
load: function() {
if (!loaded) {
request = new Ajax.Request(url, {
onSuccess: function(transport) {
loaded = true;
content = transport.responseText;
Event.fire(document, 'assetrequest:loaded', this);
},
onFailure: function() {
Event.fire(document, 'assetrequest:failed', this);
}
});
}
},
getURL: function() { return url; },
isLoaded: function() { return loaded; }
}
})());
If you try this out, you’ll soon realize something is wrong:
var req = new AbstractRequest('1', '/request1');
alert(req.getURL());
//-> '/request'
var req2 = new AbstractRequest('2', '/request2');
alert(req2.getURL());
//-> '/request2'
alert(req.getURL());
//-> '/request2' ... this isn't good!
It seems the “private” variables that are created are not instance variables, instead they are “static” variables. I then thought the only way to create private variables would be defining the variables and all private/privileged methods inside the initialize method. But that’s where I gave up since that would be just so dirty looking.
I decided to not use Class.create and instead create my own constructor, just like the Crock style. So now our example will look like:
var AssetRequest = function(_id, _url) {
var that = this,
id = _id,
url = _url,
loaded = false,
content = null,
request = null;
this.load = function() {
if (!loaded) {
request = new Ajax.Request(url, {
onSuccess: function(transport) {
loaded = true;
content = transport.responseText;
Event.fire(document, 'assetrequest:loaded', that);
},
onFailure: function() {
Event.fire(document, 'assetrequest:failed', that);
}
});
}
};
this.getURL = function() { return url; };
this.isLoaded = function() { return loaded; };
};
The problem now is that this new class can’t be extended as if it were created with Class.create. For now I only use this method for classes that don’t need to be subclassed, but hopefully in the near future, there will be an update to Prototype that will allow private variables!