Private data for objects in JavaScript

[2012-03-03] esnext, dev, javascript, jslang
(Ad, please don’t block)
Update 2012-10-21: In September 2012, The term “private name object” has been changed to “private symbol”.

JavaScript does not come with dedicated means for managing private data for an object. This post describes five techniques for working around that limitation:

  1. Instance of a constructor – private data in environment of constructor
  2. Singleton object – private data in environment of object-wrapping IIFE
  3. Any object – private data in properties with marked names
  4. Any object – private data in properties with reified names
  5. Single method – private data in environment of method-wrapping IIFE
The following sections explain each technique in more detail.

Required knowledge: While everything is explained relatively slowly, you should probably be familiar with environments and IIFEs [1] and with inheritance and constructors [2].

Instance of a constructor – private data in environment of constructor

This approach works as follows: When a constructor is invoked, two things are created: The constructor’s instance and an environment. The instance is to be initialized by the constructor. The environment holds the constructor’s parameters and local variables. Every function (which includes methods) created inside the constructor will retain a reference to the environment – the environment “in which” it was created. Thanks to that reference, it will always have access to the environment, even after the constructor is finished. The environment will stay alive as long as there is a reference to it. This combination of function and environment is called a closure, because the environment “closes over” the function’s free variables, variables that are not local to it. The constructor environment is thus data storage that is independent of the instance and only related to it because it is created at the same time. To connect the two, there must be functions that live in both worlds. Using Crockford’s terminology, an instance can have three kinds of data associated with it:
  1. Public properties: Data stored in the instance is publicly accessible.
  2. Private data: Data stored in the environment is only accessible to the constructor and functions created inside it.
  3. Privileged methods: Private functions can access public properties, but public methods cannot normally access private data. We thus need special privileged methods – functions created in the constructor that are added to the instance. Privileged methods are public and can thus be seen by non-privileged methods, but they also have access to the private data.
The following sections explain the three kinds in more detail.

Public properties

Remember that given a constructor Constr, there are two kinds of properties that are public, accessible by everyone. First, prototype properties are stored in an object that is the prototype of all instances, its properties are shared by them. That object is also accessible via Constr.prototype. Prototype properties are usually methods.
    Constr.prototype.publicMethod = ...;
Second, instance properties are unique to each instance. They are added in the constructor and usually “fields” (holding data, not methods).
    function Constr(...) {
        this.publicField = ...;
    }

Private data

The constructor’s environment consists of the parameters and local variables. They are only accessible from inside the constructor and thus private to the instance.
    function Constr(...) {
        var that = this; // hand to private functions

        var privateValue = ...;

        function privateFunction(...) {
            privateValue = ...;
            that.publicField = ...;
            that.publicMethod(...);
        }
    }
If you don’t like the that = this work-around, above, you have the option to use bind (caveat: consumes more memory):
    function Constr(...) {
        var privateValue = ...;

        var privateFunction = function (...) {
            privateValue = ...;
            this.publicField = ...;
            this.publicMethod(...);
        }.bind(this);
    }

Privileged methods

Private data is so safe from outside access that prototype methods can’t access it. But then how else would you use it, after leaving the constructor? The answer are privileged methods: Functions created in the constructor are added as instance-specific methods. They can thus access the private data and be seen by prototype methods.
    function Constr(...) {
        this.privilegedMethod = function (...) {
            ...
        };
    }

Analysis

  • Not very elegant: Mediating access to private data via privileged methods introduces an unnecessary indirection. Privileged methods and private functions both destroy the separation of concerns between the constructor (setting up instance data) and its prototype property (methods).
  • Completely safe: There is no way to access the environment’s data from outside. Which makes this solution secure if you need to guarantee that (e.g. for security-critical code). On the other hand, private data not being accessible to the outside can also be an inconvenience: Sometimes you want to unit-test private functionality. And some temporary quick fixes depend on the ability to access private data. This kind of quick fix cannot be predicted, so no matter how good your design is, the need can arise.
  • Possibly slower: Accessing properties in the prototype chain is highly optimized in current JavaScript engines. Accessing values in the closure might be slower. But these things change constantly, so you’ll have to measure, should this really matter for your code.
  • Memory consumption: Keeping the environment around and putting privileged methods in instances costs memory. Again: Be sure it really matters for your code and measure.

Singleton object – private data in environment of object-wrapping IIFE

If you work with singletons, the technique of putting private data in an environment can still be used. But, as there is no constructor, you’ll have to wrap an immediately-invoked function expression (IIFE, [1]) around the singleton to get such an environment.
    var obj = function () {  // open IIFE
        
        // public
        var that = {
            publicMethod: function (...) {
                privateValue = ...;
                privateFunction(...);
            },
            publicField: ...
        };

        // private
        var privateValue = ...;
        function privateFunction(...) {
            privateValue = ...;
            that.publicField = ...;
            that.publicMethod(...);
        }
        
        return that;
    }(); // close IIFE
Public methods can access private data, as long as they are invoked after it has been added to the environment.

Any object – private data in properties with marked names

For most non-security-critical applications, privacy is more like a hint to clients of an API: “You don’t need to see this”. That’s the core benefit of encapsulation: Hiding complexity. Even though more is going on under the hood, you only need to understand the public part of an API. The idea of a naming convention is to let clients know about privacy by marking the name of a property. A prefixed underscore is often used for this purpose. The following example shows a type StringBuilder whose property _buffer is private, but by convention only.
    function StringBuilder() {
        this._buffer = [];
    }
    StringBuilder.prototype = {
        constructor: StringBuilder,
        add: function (str) {
            this._buffer.push(str);
        },
        toString: function () {
            return this._buffer.join("");
        }
    };
Interaction:
    > var sb = new StringBuilder();
    > sb.add("Hello");
    > sb.add(" world");
    > sb.add("!");
    > sb.toString()
    ’Hello world!’

Analysis

  • Natural coding style: With the popularity of putting private data in environments, JavaScript is the only mainstream programming language that treats private and public data differently. A naming convention avoids this slightly awkward coding style.
  • Property namespace pollution: The more people use IDEs, the more it will be a nuisance to see private properties where you shouldn’t. Naturally, IDEs could adapt to that and recognize naming conventions and when private properties shouldn’t be shown.
  • Private properties can be accessed from outside: Applications include unit tests and quick fixes. But it also gives you more flexibility as to who data should be private too. You can, for example, include subtypes in your private circle, or “friend” functions. With the environment approach, you always limit access to functions created inside the scope of that environment.
  • Name clashes: private names can clash. This is already an issue for subtypes, but it becomes more problematic with some kind of multiple inheritance (e.g. via mixins or traits).

Any object – private data in properties with reified names

One problem with a naming convention for private properties is that names might clash. You can make such clashes less likely by using longer names, that, for example, include the name of the type. Then, above, the private property _buffer would be called _StringBuilder_buffer. If such a name is too long for your taste, you have the option of reifying it, of turning it into a thing (which is the literal meaning of reification).
    var buffer = "_StringBuilder_buffer";
Whereas we previously used the name directly, it is now a value (the “thing” mentioned above) stored in the variable buffer. We now access the private data via this[buffer].
    var StringBuilder = function () {
        var buffer = "_StringBuilder_buffer";
        
        function StringBuilder() {
            this[buffer] = [];
        }
        StringBuilder.prototype = {
            constructor: StringBuilder,
            add: function (str) {
                this[buffer].push(str);
            },
            toString: function () {
                return this[buffer].join("");
            }
        };
        return StringBuilder;
    }();
We have wrapped an IIFE around StringBuilder so that the variable buffer stays local and doesn’t pollute the global namespace.

ECMAScript.next and reified names

The ECMAScript.next proposal “private name objects” takes the idea of reified names one step further. Names can now also be objects, so-called private name objects. There will be a module name with a function create() that lets you create such objects:
    var buffer = name.create();
Each invocation of name.create() produces a new name object that is unique – different from any other name object created in this manner. Until the end of this section, we use the term “private property” as an abbreviation for “a property whose name is a private name object”. Compared to string names, name objects have two advantages:
  • Hidden: Private properties don’t show up when examining an object with the usual tools (Object.keys, propName in obj, etc.), they don’t pollute an object’s property name space.
  • Inaccessible: Partially as a consequence of hiding, one can only access a private property if one “has” its name object. That makes it secure: One can control precisely who has access. That control is also an advantage compared to using environments for privacy: You can now grant someone access, e.g. a unit test to check that a private method works properly.
That means: You get elegant code and security while being able to control precisely who sees what. You can, for example, let unit test code see the private name object, but no one else.

Analysis

You get all the advantages of naming conventions, while avoiding name clashes – at the expense of having to manage the reified names.

Single method – private data in environment of method-wrapping IIFE

Sometimes you only need private data for a single method. Then you can use the same technique as sharing the constructor’s environment: Attach an environment to the method, use it to hold data that has to persist across method invocations. To do so, you simply wrap an IIFE around the function defining the method. For example:
    var obj = {
        method: function () {  // open IIFE
            
            // method-private data
            var invocCount = 0;
            
            return function () {
                invocCount++;
                console.log("Invocation #"+invocCount);
                return "result";
            };
        }()  // close IIFE
    };
Interaction:
    > obj.method()
    Invocation #1
    'result'
    > obj.method()
    Invocation #2
    'result'

Analysis

For the use cases where this approach is relevant, managing the private data close to the method that uses it is very convenient. The memory consumption caused by the additional environment may be an issue.

Conclusion

We have seen that there are several patterns that you can use for keeping data private in JavaScript. Each one has pros and cons, so you need to choose carefully. ECMAScript.next will make things simpler via private name objects. It might even introduce syntactic sugar so that you don’t have to manage them manually.

Upcoming: my book on JavaScript (free online).

References

  1. JavaScript variable scoping and its pitfalls
  2. JavaScript inheritance by example
  3. Exemplars: creating objects in JavaScript