Why would Bocoup, a company whose charge is to “move the open web forward,” be publishing an article on something so nefarious-sounding as “information hiding”? An article titled “Free Love & Information in JavaScript” would seem much more apt for this blog. Trust me: if information hiding were an inherently immoral practice, I wouldn’t know anything about it. I probably would have spent most of high school trying to convince my peers that I was actually way into information hiding (despite being afraid of it).
In terms of programming, “information hiding” refers to the practice of concealing implementation details that are unfit for the consumers of the code. (As per usual, Wikipedia offers a nice overview.) This prompts the question: what do we mean by “consumers”?
- If you’re working on a library, the “consumers” would be the developers using your library in their application logic. In those cases, you would want to “hide” methods that aren’t part of the API but that you wrote for internal use (for example, you might want to DRY out your functions by sharing code between them).
- If you’re writing an application, “consumers” might refer to other developers on the project who use your module. As before, you wouldn’t want consumers to rely on details that you intended to change later on.
- Since the browser is such an open execution environment, “consumers” could also refer to end users of the front-end application you are writing. Here, you might not want users to open up the JavaScript console and start tinkering with sensitive data and methods.
Whatever your particular perspective, I hope you can see how information hiding can be a useful practice when it comes to structuring code. Just like smoking, dancing, and attending the prom, it isn’t scary and it doesn’t have to end with you sobbing in your mother’s arms. If you’re with me on that, it’s time to take a look at how we might accomplish this in JavaScript.
Implementation Details
Like with most things JavaScript, web developers have a variety of options when
it comes to hiding implementation details. Here, I’d like to talk about five
distinct approaches: informal naming, per-instance closures, per-class
closures, obscurity, and symbols. For each approach, I’ll include a code
example for a simple Player
class that implements:
- private state for
coins
andlives
- a private
cashIn
method - a public
addCoin
method
In all cases, the usage of the API will remain the same:
// Instantiate a player
var player = new Player();
// Award the player with two coins:
player.addCoin();
player.addCoin();
Informal Naming
A common convention among JavaScript developers is to simply prefix the names
of “private” data and functions with an underscore (_
) character. Many open-
source JavaScript libraries follow this practice including
jQuery, Backbone.js,
Ember.js, and Knockout.js.
Because of this adoption, the practice serves as a reliable signal to consumers
that, “hey, don’t use this–I might change it later”. Here’s how it’s done:
function Player() {
this._lifeCount = 3;
this._coinCount = 0;
}
// The underscore in front of `_cashIn` tells consumers that this method is not
// intended for public use.
Player.prototype._cashIn = function() {
this._lifeCount += Math.floor(this._coinCount / 100);
this._coinCount %= 100;
};
Player.prototype.addCoin = function() {
this._coinCount++;
if (this._coinCount > 99) {
this._cashIn();
}
};
Of course, simply prefixing a method name with an underscore doesn’t stop anyone from using it. This means that if you are interested in preventing consumers from monkeying around with your application or SDK (maybe cheating at a game or gaining unauthorized access), you’ll need to keep reading.
Per-instance closures
The “closure” in JavaScript can be a tricky beast, especially for developers just getting started with the language. Basically, a closure is created whenever a long-lived function holds a reference to (or “closes around”) a short-lived function. If we define implementation details within the scope of the constructor function, then public methods can “close” around them. This is starting to feel a bit academic, so let’s return to the running example:
function Player() {
var lifeCount = 3;
var coinCount = 0;
// When defined this way, `cashIn` will not be available outside of the
// constructor.
function cashIn() {
lifeCount += Math.floor(coinCount / 100);
coinCount %= 100;
}
// We'll declare `addCoin` as an instance method by attaching it to `this`.
this.addCoin = function() {
coinCount++;
if (coinCount > 99) {
cashIn();
}
};
}
Here, you can see that cashIn
cannot be accessed outside of the Player
constructor. By closing over that function, however, the addCoin
function can
use it. We attach the addCoin
method to the instance itself because, as a
public API, we want this to be accessible to the consumer.
This approach suffers from two problems. The first relates to performance.
Thanks to the concept of “prototypal inheritance”, instance methods in
JavaScript are shared by all instances (classically-trained programmers may
recognize this as the “flyweight pattern” described by the “Gang of
Four”).
This technique of information hiding eschews the performance benefits of code
sharing–each instance defines a unique copy of the addCoin
and cashIn
methods.
Secondly, structuring code in this way does not scale particularly well. Any
Player
method that needs to access the private API must be declared within
the constructor. This requirement will encourage growth of the constructor
function, making it more and more difficult to read and maintain.
Per-class closures
Instead of using the constructor as a closure for private methods, we could declare private methods statically and then close around the constructor and methods with an IIFE.
var Player = (function() {
function Player() {
this.lifeCount = 2;
this.coinCount = 0;
}
// The private `cashIn` function is not accessible outside the IIFE's scope
function cashIn() {
this.lifeCount += Math.floor(this.coinCount / 100);
this.coinCount %= 100;
}
Player.prototype.addCoin = function() {
this.coinCount++;
if (this.coinCount > 99) {
// We use "call invocation" to make sure the context of the `cashIn`
// function is set to this instance of `Player`
cashIn.call(this);
}
};
// We need to explicitly "export" the `Player` class so that it is available
// outside the scope of the IIFE
return Player;
})();
This approach successfully hides private methods, and those methods are
shared by all Player
instances. But slow down there, this approach isn’t
perfect, either. You probably noticed that the instance variables lifeCount
and coinCount
are exposed for all the world to see. This “per-instance
closure” approach only works for private methods.* So really, this approach is
too niche to be generally useful.
Obscurity
Let’s take a harder look at the “informal” approach we first considered. That method was nice because it was memory-efficient and maintainable and because it supported both instance methods and instance data. If we could find a way to make those underscore-prefixed attributes truly private, we might have a real solution on our hands…
It turns out, we can! Sort of! Instead of hard-coding the private attributes
with human-readable strings (i.e. "_addCoin"
or "_lives"
), we can name them
with dynamic randomly-generated strings. Then, we can keep a lookup table to
translate human-readable names to their randomly-generated counterparts (and
hide that inside a closure).
Not sure what I’m talking about? Neither am I, at this point. Let’s return to the example for some clarity:
// We'll use an IIFE again so that our key isn't globally available
var Player = (function() {
// This is our map. Each time this code executes, the values of this object
// will be unique.
var KEY = {
coinCount: Math.random(),
lifeCount: Math.random(),
cashIn: Math.random()
};
function Player() {
this[KEY.lifeCount] = 3;
this[KEY.coinCount] = 0;
}
Player.prototype.addCoin = function() {
this[KEY.coinCount]++;
if (this[KEY.coinCount] > 99) {
this[KEY.cashIn]();
}
};
Player.prototype[KEY.cashIn] = function() {
this[KEY.lifeCount] += Math.floor(this[P.coinCount] / 100);
this[KEY.coinCount] %= 100;
};
return Player;
})();
In this example, instead of using “dot notation” to dereference the Person
instance with a simple string (as in this.lifeCount
), we’re using the KEY
lookup table to retrieve the obscured name** (as in KEY.lifeCount
), and
using that name to dereference the instance (as in this[KEY.lifeCount
).
Notice how all this does not change the public API: person.addCoin()
still
works as intended.
This solution is perfect, isn’t it?! Actually, it’s a nightmare. First of all, who wants to write code like this? I don’t.
Second of all, we’re not so much hiding the information as we are obscuring it
(you might say we’re hiding it in plain sight). If you were to inspect a
Person
instance in your browser’s JavaScript Console, you would see that it
defined two numeric attributes and one Function attribute (albeit with crazy
names like 0.5115215787664056
). While this makes it very difficult for a
consumer to accidentally depend on these details (they change every time you
refresh the page), any dedicated adversary could probe at them enough to
reverse engineer our KEY
lookup table. We could frustrate these efforts by
making the private properties non-enumerable with
Object.defineProperty
,
but that will only work in browsers that implement ECMAScript
5.
So while this will look great on our Wall of JavaScript Oddities, it probably has no place in production.
Symbols
For our last attempt to tackle this problem, we’ll be looking at functionality that doesn’t exist today. ECMAScript 6 (sometimes referred to as “ES6” or by its code name, “Harmony”) is the next version of the JavaScript language specification. It includes a lot of exciting new features, but for the purposes of this post, we’ll be focusing on Symbols. (If you’d like to learn more about ES6, you should watch our very own Rick Waldron‘s presentation, “ECMAScript 6: My Favorite Parts”.)
One word of warning: ES6 is not a finalized standard. Symbols are still being discussed, which means that the precise syntax discussed here may change over time. (It also means that you can participate in its definition–head on over to the es-discuss mailing list to get involved.)
That said, lets take a look at what Symbols are (conceptually), and why they might be useful in our goal to hide information. As you probably know, in today’s JavaScript, any value you specify as a key to an object is automatically coerced into a String. For example:
var myObject = {};
var objectKey = {};
// When we attempt to use an object as a key...
myObject[ objectKey ] = 4;
// It is coerced to a string, meaning the value is actually stored with the key
// '[object Object]' (the value returned by `Object.toString()`)
myObject['[object Object]'] === 4;
// This means that even though we might want to use a different object as a
// unique key for a different value...
myObject[ { a: 23 } ] = 6;
// ...the same attribute will be modified, since by default, all object share
// the same generic String representation.
myObject['[object Object]'] === 6;
Symbols are objects specifically designed to avoid this behavior: when used as keys to an object, they will not be coerced to Strings.
If we encapsulate the Symbols, then we can use them to define “private” attributes of publicly-accessible objects–true information hiding in JavaScript! Here’s how it might be done:
var Player = (function() {
// Define the Symbols that we'll use as keys for the private API
var lifeCount = Symbol(),
coinCount = Symbol(),
cashIn = Symbol();
function Player() {
// When used to dereference the `Player` instance, Symbols will not be
// converted to String values
this[lifeCount] = 3;
this[coinCount] = 0;
}
Player.prototype.addCoin = function() {
this[coinCount]++;
if (this[coinCount] > 99) {
this[cashIn]();
}
};
Player.prototype[cashIn] = function() {
this[lifeCount] += Math.floor(this[coinCount] / 100);
this[coinCount] %= 100;
};
return Player;
})();
This should look familiar–it’s basically identical to the “Obscurity” approach described previously (with Symbols replacing random numbers). Given the similarities, it’s reasonable to wonder if it’s actually an improvement at all. Because Symbols are unique objects in memory, they cannot be “forged” or “guessed” in the same way that string values can. We rejected the “Obscurity” approach because of this very weakness in String keys, so Symbols address the only flaw with that approach.
But wait! There’s more! The square brackets all over the above example can be kind of a drag to write. Lucky for us, an alternative syntax for working with Symbols in the context of ES6 modules makes them even more readable.
As I’ve already pointed out, ECMAScript 6 is still being specified. Different
features have reached different levels of consensus. There’s no telling how
“at-names” and the private
keyword might change change as ES6 matures. What
I’m about to show you is volatile–my brow was sweating and my hands were
shaking as I painstakingly typed it all out:
var Player = (function() {
// Define private Symbols using the "at-name" syntax
private @lifeCount, @coinCount, @cashIn;
function Player() {
// Use the Symbol with dot notation to dereference the Player instance!
this.@lifeCount = 3;
this.@coinCount = 0;
}
Player.prototype.addCoin = function() {
this.@coinCount++;
if (this.@coinCount > 99) {
this.@cashIn();
}
};
Player.prototype.@cashIn = function() {
this.@lifeCount += Math.floor(this.@coinCount / 100);
this.@coinCount %= 100;
};
return Player;
})();
This code just feels so much cleaner without all those square brackets, doesn’t
it? Eagle-eyed readers will note that this code looks eerily similar to the
code in the first “Informal Naming” approach. Really, the only differences are
the initial declaration of symbols, and a replacement of the underscore
character (_
) with the “at” sign (@
). I give a lot of credit to the
language designers for this fact. This syntax recognizes the informal
convention already in use today and “makes it real” through a trivial
transformation.
I know I said that ECMAScript 6 doesn’t exist today, but we don’t have to let that stop us from playing with these ideas. Just like Dr. Emmit Brown, we can experiment with these visions of the future to create something remarkable. The Continuum project gives us a glimpse as to what it might be like to write JavaScript with next-generation features including, you guessed it, Symbols.
Conclusions
It’s been a long road, but we finally found a complete solution for information hiding in JavaScript… or did we? After all, ECMASript 6 still isn’t complete, let alone implemented in enough browsers for general use. Where does that leave us, the modern-day developers yearning for private state?
For my part, I’ll be sticking to the informal naming convention for the foreseeable future. No other approach is as recognizable, maintainable, or powerful as simply denoting private APIs with an underscore.
We can’t forget that convention alone will not stop malicious adversaries, but that problem is much bigger than information hiding. At the end of the day, the browser remains an inherently insecure execution environment; no amount of JavaScript trickery can fix that. If you are writing client-side code that needs to be trusted, my advice is to off-load sensitive operations to a secure server.
This may be a disappointing conclusion to our investigation, but sometimes simplicity trumps all other requirements.
* – In environments that implement WeakMaps from ECMAScript 6, you could
build a WeakMap that associated Player
instances with private data, but as
we’ll see, ES6 promises a far more convenient primitive for hiding
information.
** – As avid readers of this blog know, Math.random()
isn’t sufficiently
random for cryptographic
applications, but it should do for
our purposes.