Skip To Main Content

Information Hiding in JavaScript

Posted by Mike Pennisi

Jun 18 2013

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 and lives
  • 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.

Comments

We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.

Contact Us

We'd love to hear from you. Get in touch!