Hyper-private variables in JavaScript

Richard Cornford and Douglas Crockford investigated much of what can be done with closures in JavaScript to create new variable scopes and emulate the concept of private variables that languages like Java support. Douglas Crockford introduced the idea of secret variables and privileged methods that can access those secret variables. In the examples given on his page, the technique simulates private variables and instance methods in languages like Java where any instance method can access any of the private variables.

It is often a best practice to create a getter and setter for a private variable and use the getter and setter in all other methods even in the object rather than directly accessing the variable. This completely encapsulates the private variable. This is a best practice but to enforce this practice we need a more restricted scope than private. In JavaScript we can create a hyper-private variable that is only accessible to the getter and setter.

Below is an example that shows the encapsulation of hyper-private variables. The benefits of instance variable encapsulation are shown with capitalization of the first name, storing the year not the age, and not permitting negative ages.

function Person(first, age) {

    // prvate methods
    var getFirstName,
        setFirstName,
        getAge,
        setAge;

    (function() {
        // _first is a hyper-private variable.
        // _first only visible to getFirstName and setFirstName.
        var _first;
        getFirstName = function() {
           // captialize the name
           return _first.charAt(0).toUpperCase() + _first.substr(1);  
        };
        setFirstName = function(v) {
            _first = v;
        };
    })();

    (function() {
        // _year is a hyper-private variable.
        // _year only visible to getAge and setAge.
        // Store birth year instead of age so that 
        // getAge always returns current age.
        var _year;
        getAge = function() {
           return (new Date()).getYear() - _year;  
        };
        setAge = function(v) {
            if (v < 0) {throw new Error('negative ages not allowed');}
            _year = (new Date()).getYear() - v;
        };
    })();

    // sayInfo is public method.
    // Cannot access _first or _age directly
    // so must use the getters.
    this.sayInfo = function() {
        alert(getFirstName() + ' ' + getAge());
    };

    // Cannot access _first or _age directly
    // so must use the setters.
    setFirstName(first);
    setAge(age);
}

var gisele = new Person('Gisele', 27);

gisele.sayInfo();

Comments

Have something to write? Comment on this article.

David Golightly October 16, 2007

This is a nice pattern, similar to Lazy Function Definition; unfortunately, I would say it should probably be used sparingly. Function calls in JavaScript carry 3x the runtime overhead of simple property lookups. In general, while we can indeed write getters and setters to fully encapsulate data, ECMA v3 as an interpreted language is not well equipped to support that without taking a significant performance hit.

Additionally, in your example, using Person as a constructor means that for a full-featured prototype-based constructor, with function properties added to the constructor's prototype object, none of these publicly exposed methods will have access to the hyper-private getter/setter functions unless you include the function definitions themselves within the constructor function-which should not be considered a best practice, given that these member function objects would be duplicated for each instance object made from the constructor. For example:

function Person(first, age) {
    // .. hyperfunctions get/setFirstName and get/setAge
    // defined as above
}

Person.prototype.getName = function () {
    // this function does not have access to the getter it needs
}

var p1 = new Person('steve', 3);
var p2 = new Person('bill', 123);

Here, p1 and p2 both share the same function object in memory, that assigned to Person.prototype.getName. Now consider:

function Person(first, age) {
    // ... again, the hyperprivates defined here ...
    this.getName = function () {
        return getFirstName();
    }
}

var p1 = new Person('steve', 3);
var p2 = new Person('bill', 123);

In the bottom example, p1 and p2 each have their own independent copies of the Function object assigned in "this.getName". This becomes a problem when large numbers of member functions are used or when large numbers of instances are created using "new Person".

Peter Michaux October 20, 2007

David,

Thanks for the comments. I completely agree with everything you have written. A big part of making this post was just realizing another interesting thing that can be done with JavaScript closures and scoping.

Joe Grossberg October 26, 2007

Wow, a great post and a great follow-up comment. What has blogging come to! ;)

Bryan December 28, 2007

Heh. I love this sort of niftiness, just kind of 'discovered' it about a week ago.

Anyway, I'd written a Prototype-like function for adding a shared private namespace to any function (for privates in Class-like constructs, for example). Mind you, it's not ideal; it re-evaluates a function.

Function.prototype.setNameSpace = function () {
  var scope={};
  if (typeof this.scope=='undefined')
    this.scope={};
    (function () {
      for (i=0; i<arguments.length; i++)
        for (v in arguments[i])
          scope[v]=arguments[i][v];
    }).apply(this,arguments);
    with (scope) {
      eval('var ret='+this.toString());
    }
    return ret;
}

Now, you can create a meta-function that has available to it as variables the properties of an object, for example:

var myFunc = (function () {
  alert(privateVar);
}).setNameSpace({privateVar:'No one can see me but you!'});
haysmark June 26, 2008

Sorry people, here is how you bypass it:

eval("_first='owned'", eval("getFirstName", eval("sayInfo", gisele)));
gisele.sayInfo(); // owned

This eval syntax is listed in Mozilla's MDC. You can bypass "private" variables by running the eval in the context of the getter function. You can get the getter in turn with the help of the public function that accesses it (sayInfo), which you can get in turn with the object instance.

So long as you are using named variables, you will not be able to prevent people from exposing access to them.

The full source:

function Person(first, age) {

    // prvate methods
    var getFirstName,
        setFirstName,
        getAge,
        setAge;

    (function() {
        // _first is a hyper-private variable.
        // _first only visible to getFirstName and setFirstName.
        var _first;
        getFirstName = function() {
           // captialize the name
           return _first.charAt(0).toUpperCase() + _first.substr(1);  
        };
        setFirstName = function(v) {
            _first = v;
        };
    })();

    (function() {
        // _year is a hyper-private variable.
        // _year only visible to getAge and setAge.
        // Store birth year instead of age so that 
        // getAge always returns current age.
        var _year;
        getAge = function() {
           return (new Date()).getYear() - _year;  
        };
        setAge = function(v) {
            if (v < 0) {throw new Error('negative ages not allowed');}
            _year = (new Date()).getYear() - v;
        };
    })();

    // sayInfo is public method.
    // Cannot access _first or _age directly
    // so must use the getters.
    this.sayInfo = function() {
        alert(getFirstName() + ' ' + getAge());
    };

    // Cannot access _first or _age directly
    // so must use the setters.
    setFirstName(first);
    setAge(age);
}

var gisele = new Person('Gisele', 27);

eval("_first='owned'", eval("getFirstName", eval("sayInfo", gisele)));

gisele.sayInfo(); // prints Owned 27
Peter Michaux June 27, 2008

haysmark,

Thanks for the comment. I don't think about the second argument extension in JavaScript(TM) very frequently. I've posted a new article about this to see what other's have to say.

Hallvord R. M. Steen July 4, 2008

Small comment: please use getFullYear() instead of getYear(). The latter returns "years since 1900" in many implementations - though in some of them this happens only for certain year ranges - and should be avoided because of this inconsistency.

Milan Adamovsky October 29, 2010

I have a slightly more lightweight solution to the problem that I blogged here http://milan.adamovsky.com/2010/10/closures-with-getters-and-setters-in.html

Erin McDonald February 16, 2011

I just heard about this new java script library called jPaq. Since it is totally customizable unlike most other libraries of its caliber, I decided to look at some of the source code. After reviewing it I noticed that Chris West came up with a nice way of implementing truly private variables in his Color object. He uses closures to allow his prototype functions to access private variables through a private member accessor function. I doubt I can explain it as well as some other people like Resig or Dean Edwards, but I would definitely look at it if I were you. Here is a link to the site: http://www.jpaq.org/

Have something to write? Comment on this article.