Privy, Private Members for Objects in JavaScript
People who say you can't have private members in JavaScript are just plain wrong. JavaScript had private members right from the beginning in the form of functions. Variables defined in a function are only available in the scope of that function.
var Counter = function () {
var value = 0;
this.get = function () {
return value;
};
this.add = function (other) {
value += other;
};
};
var counter = new Counter();
counter.value; // undefined, of course
counter.get(); // 0, encapsulation, hurrah!
These are called privileged methods as they have access to variables defined in the constructor. Like most things they come at a cost. For each object you create you must also create new functions for them, this could be wasteful.
N.B. Now before we continue, I am by no means saying this is wasteful or that under X circumstances with a blue moon and a cherry on top that my method is better than yours. This is mearly a demonstration that it is possible to securely access private members through prototype methods.
So lets begin.
As I've previously shown, privacy and encapsulation is done through function expressions in JavaScript. value was defined within the function as was only accessible through other functions due to closure.
Using closures we can create other interesting types of objects; one of which is a sealer. A sealer provides functionally takes an object and returns a means of getting it back. An example of one can be seen below.
var createSealer = function () {
var value, key, sealer = {};
// Seals `object` and returns one time key to retreive it
sealer.seal = function (object) {
value = object;
return key = {};
};
// Returns the sealed object _once_ if `given` was used to seal it
sealer.open = function (given) {
var object = value;
// Only give object if key is correct
if (given === key) {
// Empty the sealer
value = undefined;
return object;
}
};
return sealer;
};
This sealer has two methods, seal which takes a object and returns a key, and open. seal which takes a key and returns an object if it was the correct key. This particular sealer enforces a one time key rule, meaning that when the object is retrieved through open it cannot be done so again.
Douglas Crockford did an excellent job of explaining sealers and unsealers in his Crockford on JavaScript series. I would definitely recommend you watch all the videos if you find this interesting.
What is useful about this is that we can give the respective methods to a pair of object and allow them to communicate over a untrusted channel.
var sealer = createSealer(),
a = new A(sealer.seal, untrusted),
b = new B(sealer.open, untrusted);
var A = function (seal, channel) {
var key = seal("secret");
channel.send(key);
};
var B = function (open, channel) {
var key = channel.read();
open(key); // "secret"
};
Why is this important? We can't trust the carrier channel, so we pass each object an opener or sealer that uses the key's identify to accessed the passed object.
There is no problem at all in the untrusted party having the key that because they lack the means to get at the important data, sealer.open.
Enter Privy
This is how Privy works. Each time you create an object constructor you must first wrap it in a Immediately-Invoked Function Expression (IIFE) creating the privacy need to communicate between objects and Privy.
In order to give each object it's own private object we assign it a function (defaultly given the property _) that closes over the constructor IIFE. The function's purpose is to seal the privates with a sealer; the same sealer of which we have the opener. When called, the function will simply return the key to open up the privates.
To see this in action let me define a class object constructor and prototype methods. This should be familiar to you if you have see a CoffeeScript class compiled to JavaScript.
var Person = (function () {
// Here, the '_' isn't necessary as it is the default
var p = Privy.create('_');
function Person(name) {
// Create the _ property on `this`
// as a conviencce it also returns the privates
var privates = p.initiate(this);
// Set a private member
privates.name = name;
}
Person.prototype.name = function () {
var key = object._(),
privates = p.sealer.open(key);
return privates.name;
};
}());
var thomas = new Person('Thomas');
thomas.name() // "Thomas"
What is unqiue about this code is that the privates are really private as well as being accessible through prototype methods. The caveat is that they can only be access within the IIFE, that is to say, anything in scope of p.
You will notice that the prototype method is doing the magic.
Person.prototype.name = function () {
var key = object._(),
privates = p.sealer.open(key);
return privates.name;
};
The special function _ returns the key and p.sealer, which is available to all Person objects, contains the opener to retrieve the privates. This turns out to be something you need to do quite often if you want access to the privates. To trivialise this the Privy object p is actually a function that does this for you. As such, the name method can now be:
Person.prototype.name = function () {
return p(this).name;
};
Now one thing to note is that p works on any Person object, and this works great for us in terms on Object Orientation. This means any person given reference to another person can see their privates (giggity); just like in other programming languages. So lets alter the class object constructor and prototype methods to see if two people are of the same age.
var Person = (function () {
var p = Privy.create();
function Person(name, age) {
var privates = p.initiate(this);
privates.name = name;
privates.age = age;
}
// ...
Person.prototype.sameAge = function (person) {
return p(this).age === p(person).age;
};
}());
var thomas = new Person('Thomas', 22),
sarah = new Person('Sarah', 22);
thomas.sameAge(sarah); // true
The benifit of having this is there only needs to be one set methods for each constructor and they can all facilitate their concerned objects. There is no need have privileged methods to get access to privates. But like everything, this comes at a cost. A function is added to every object you create to using this as well as one Privy object needed per class. It also takes 2 extra function calls to access the private object, one to seal, one to open.
Your millage may vary, but if you are creating a large number of objects that have some private data, this may save you in the long run. I have no scientific data to back that up however. Ultimately this was just a thought of mine that happened to turn into an actual GitHub repo which I am proud of.