This post was originally published on my old back on September 26th, 2011. The content provided here is a republishing of the original post.
Most folks know how to mimic public, private, and “privileged” variables in Javascript, but I think at some point, when getting familiar with prototypal inheritance, most scripters wonder if there is a way to access the private members (the ones created in a closure) of an instance within a function defined using prototype.
There is really no good way to do this. Here’s a simple prototype example:
function Cat(name, age) { this.name = name; this.age = age; } Cat.prototype.introduce = function() { alert(‘I am a cat. My name is ‘ + this.name + ‘, and I am ‘ + this.age + ‘ year(s) old.’; }; var c = new Cat(‘Garfield’, 7); c.introduce();
The introduce
function was defined with .prototype
so the this
keyword in that context refers to the Cat instance in question. Using it, we can get at the .name
and .age
properties. But this also means that the introduce
function isn’t the only thing that has access to those properties. In fact, the whole world has access. They are, essentially, public properties.
alert(c.name); // directly accessible alert(c.age); // directly accessible
What if you don’t want that? What if you want to take the name that is passed into the constructor, modify it in some way (let’s say, convert it to uppercase) and then offer read only access to it?
function Cat(name, age) { this.name = name.toUpperCase(); } Cat.prototype.getName = function() { return this.name; }; var c = new Cat('Garfield', 7); alert(c.getName()); // GARFIELD c.name = 'STEPHEN'; // oops, should this be allowed? alert(c.getName()); // STEPHEN, clearly not the kind of encapsulation we're going for
So how can we allow getName
to have access to .name
, but at the same time disallow access to .name
outside of prototype defined functions? Well, as I said, you really can’t. I mean, there’s no good way. But there is a sort-of way.
Alright. With the disclaimer out of the way, here we go:
var Cat = function() { var guid = 0; var privates = {}; function cat(name, age) { this._$guid = guid++; privates[this._$guid] = { name: name, age: age }; } cat.prototype.getName = function() { return privates[this._$guid].name; }; cat.prototype.getAge = function() { return privates[this._$guid].age; }; return cat; }(); var cat1 = new Cat('Garfield', 7); var cat2 = new Cat('Fluffy', 13); alert(cat1.getName === cat2.getName); // true alert(cat1.name); // oops, no dice (which is good) alert(cat1.getName()); // Garfield alert(cat2.age); // oops, no dice alert(cat2.getAge()); // 13
First, I alert cat1.getName === cat2.getName to illustrate that prototype really is being used here. The instances cat1 and cat2 share the implementation of the getName function (they don’t each get their own — something that would have been true had we used the closure model exclusively).
Second, you’ll notice that I do not have direct access to .name
or .age
. So this seems to do what we want.
Now, more alert readers will no doubt have noticed that I cheated. I’m still using the this
keyword from within my prototype defined functions, getName
and getAge
. This is so that I can access the instances GUID (._$guid
). The GUID is, of course, nothing more than counter, but it serves as a way to track the created instances. Using it, we can get access to property bag in the privates
hash that “belongs to” the particular Cat instance we’re interested in.
But there is a flaw here, of course. The ._$guid
, despite its marginally cryptic name, is completely unprotected. That is, the whole world has access to it. So one could really hork things badly if he decided to do something like:
var garfield = new Cat('Garfield', 7); var fluffy = new Cat('Fluffy', 10); fluffy._$guid = 0; alert(fluffy.getName()); // Garfield, oops! this is NOT good
Now, in this example, I chose a valid GUID (that of Garfield), but I could have just as easily chosen an invalid GUID, which would have caused a script error. Of course, the script error is easily preventable by ensuring that the ._$guid
actually exists as a key in the privates
hash before attempting to access it, but this does nothing to ensure the integrity of the data, and that’s a problem.
Turns out though, that we can add a bit more code. A bit of code that involves a check prior to accessing data from the privates
hash to ensure that the instance’s _$guid
hasn’t been tampered with. If it has, we throw an exception and refuse to go any further.
Here’s the revision:
var Cat = function() { var guid = 0; var privates = {}; var check = {}; function cat(name, age) { this._$guid = guid++; check[this._$guid] = this; privates[this._$guid] = { name: name, age: age }; } cat.prototype.getName = function() { if (check[this._$guid] !== this) { throw 'Oops, looks like this instances has been tampered with.'; } return privates[this._$guid].name; }; cat.prototype.getAge = function() { if (check[this._$guid] !== this) { throw 'Oops, looks like this instances has been tampered with.'; } return privates[this._$guid].age; }; return cat; }(); var fluffy = new Cat('Fluffy', 7); var snowball = new Cat('Snowball', 12); var garfield = new Cat('Garfield', 9); alert(fluffy.getName()); // Fluffy alert(snowball.getName()); // Snowball alert(garfield.getName()); // Garfield fluffy._$guid = 2; alert(fluffy.getName()); // Throws an exception, b/c fluffy's _$guid was modified
I even created a nice illustration to help you visualize this
As you can plainly see, the keys in the privates
hash correspond to the ._$guid
property of Cat instance. In other words, if you match up an instance’s ._$guid
with a key in the privates
hash, you’ll have access to that instance’s collection of member variables.
You’ll also notice that the keys in the check
hash match the keys in the privates
hash. However, they don’t point to a property bag, they point to an actual Cat instance. In the getName
function, we have access to the instance (through the use of the this
keyword), and we can use that to perform a check. If an instance’s public ._$guid
has been tampered with, then looking in the check
hash for that $_guid
will result in accessing a Cat instance that isn’t equal (identical) to this
. In that case, we can throw an exception. Otherwise, we go ahead and access and entry from the privates
hash, and this gives us access to the current Cat’s members.
Before closing, I want to once again make clear that I am most definitely NOT recommending that you use this model, but neither am I discouraging the use of it (I’m a little bit annoying that way, I know). It’s simply untested in terms of memory and speed performance. Whether or not this yields anything that would constitute acceptable performance, I do not know and have not attempted to determine. I imagine there could be some scenarios in which it is probably okay. And I imagine there are other scenarios in which it might be a nightmare, but I’m only guessing on both counts.
If you decide to experiment with this, definitely report back here with your findings for all to see.
Happy scripting!