Back to homepage.

Writings.

Private and protected fields in JavaScript

Information hiding is an important technique in building maintainable software systems; by providing access to only the minimal interface of a module, changes can be made to the internal implementation without fear of breaking other modules that use it. Many object-oriented programming languages such as Java and C++ provide private and protected methods and fields to help enforce this; a private field can only be accessed by methods in the class in which it is defined, protected ones can also be accessed by methods in subclasses. There have been a number of attempts to add private and protected methods and fields to JavaScript objects, but all are either easily circumvented or clumsy and inefficient. For example, the Mootools implementation of protected methods allow them to be called if there is a method from the class anywhere in the call stack. What does this mean in plain(er) English? Any callback function provided to a method will be able to access the protected methods in that class:

var testobj;

    function thisShouldRaiseAnError() {
        return testobj.getProtectedValue();
    }

    var MooClass = new Class({
        getProtectedValue: function() {
            return 'top secret value';
        }.protect(),
        callback: function(f) {
            return f();
        }
    });

    testobj = new MooClass();

    // This should raise an exception but doesn't:
    testobj.callback(thisShouldRaiseAnError));

Douglas Crockford adds private methods and variables by defining them in the constructor, but this is both inefficient (creating a new function object every time the constructor function is called) and means they cannot be accessed by any methods added subsequently to the constructor prototype or by methods defined on subclasses. What we want is to be able to define classes a bit like this:

var AClass = new Class({
    init: function(name) {
        this.privateVar = name;
    },
    methodOfAClass: function(){
        return 'Private: ' + this.privateVar + ' and Protected: ' + this.protectedVar;
    },
    inheritanceTest: function() {
        return 'Message from AClass';
    },
    'private privateVar': 'a private variable',
    'protected protectedVar': 'protected variable'
});

var ASubClass = new Class({

    Extends: AClass,

    init: function(name, publicnumber) {
        // Note call to parent constructor:
        this.parent('init', name);
        // This var has not been declared private or protected
        // so will be public:
        this.publicdata = publicnumber;
    },

    inheritanceTest: function() {
        return this.parent('inheritanceTest') + ' and Message from ASubClass';
    },

    redefineProtectedVariable: function() {
        this.protectedVar = 'new protected value';
    },

    getPrivate: function() {
        return this.privateVar;
    },

    getProtected: function() {
        return this.protectedVar;
    }
});

Which should then be usable like this:

> var obj = new AClass('test');
> obj instanceof AClass;
  true
> var x = obj.privateVar;
  Error: cannot access private privateVar property
> var y = obj.protectedVar;
  Error: cannot access protected protectedVar property
> obj.methodOfAClass();
  'Private: a private variable and Protected: protected variable'
> var obj2 = new ASubClass('test2', 42);
> obj2 instanceof ASubClass
  true
> obj2 instanceof AClass
  true
> obj2.getProtected(); // Methods can read inherited protected variable
  'protected variable'
> var a = obj2.protectedVar; // But you cannot access it directly
  Error: cannot access protected protectedVar property
> obj2.getPrivate(); // Cannot access private field in superclass.
  Error: cannot access private privateVar property
> obj2.redefineProtectedVarible(); // Methods have write access too.
> obj2.getProtected();
  'new protected value'
> obj2.ineritanceTest();
  'Message from AClass and Message from ASubClass'
> var b = obj2.protectedVar; // Note the field is still protected
  Error: cannot access protected protectedVar property

Is this possible? Well, surprisingly yes, but sadly not in all browsers. Still, there are some interesting things to look at. The code is presented below, with comments to help explain some of the trickier points. The general class function is fairly similar to that in Mootools, which itself was originally, I believe, based on Dean Edwards’ base2. However, there are significant differences. For example, Mootools wraps every function (and the constructor) in another function; my version wraps neither, thereby reducing the number of function calls, which should be more efficient. The only compromise that must be made because of this is that calls to this.parent (i.e. a call to an overridden function in the parent class) must include the name of the method as an extra first argument. I do not consider this a significant issue, but feel free to disagree (yes, it does allow you to call an overridden method with a different name to the calling method; this could be considered a bonus or a disadvantage depending on how you look at it). There is a lot to take in here, so if you’re not comfortable with the concept of closures, prototypes and the call stack, you might need to do a little research elsewhere.

var Class = (function() { // Open closure

    // Functions within the closure can only be accessed by other functions
    // within the closure (another form of private methods)
    function callParentMethod(name) {

        // The object calling this function will set itself as the 'this' parameter.
        // The constructor field (inherited through the object prototype)
        // points to the constructing function for the object. In the Class
        // setup function we add a pointer from this to the parent constructor
        var method = this.constructor.parent;

        // If the method name is not init, we need to look in the parent
        // constructor's prototype for the desired method.
        if (method && name !== 'init') {
            method = method.prototype[name];
        }
        if (method instanceof Function) {
            return method.apply(this, Array.prototype.slice.call(arguments, 1));
        }
        throw "Method " + name + " not found in superclass.";
    }

    function Class(params) {

        if (params instanceof Function) {
            params = {init: params};
        }

        // 'this' is a reference to a new Class object,
        // so mixin everything from the prototype into the
        // actual class constructor
        var newClass = (params.init || function(){});
        for (var prop in this) {
            newClass[prop] = this[prop];
        }
        delete params.init;

        var parent = params.Extends;
        if (parent) {
            // Create new parent object as prototype
            newClass.prototype = Class.instantiate(parent);
            // Set pointer from constructor to parent constructor
            newClass.parent = parent;
            // If not already implemented by parent prototype, implement
            // the parent method for calling superclass methods.
            newClass.implement('protected parent', callParentMethod, true);
            delete params.Extends;
        }

        newClass.implement(params);

        newClass.constructor = Class;
        newClass.prototype.constructor = newClass;

        return newClass;
    }

    Class.instantiate = function(constructor) {
        var f = function(){};
        f.prototype = constructor.prototype;
        return new f();
    };

    // This function checks whether a certain method belongs to a certain
    // class (i.e. whether it is contained within the class's prototype object
    // Remember, the constructor function is also in the prototype).
    // For protected fields, the prototype of the calling object's constructor is passed.
    // For private fields, the prototype in which the variable is defined is passed
    function methodInPrototype(proto, method, mayBeInSuperClass) {

        // If access attempted in global context, method may be null
        if (!method) return false;

        // Implemented methods have a _name property set.
        // Note that altering this will not allow access for
        // functions not in the class (but will prevent
        // the method from accessing the data it's supposed to; this
        // could be fixed by iterating through all properties in the constructor
        // looking for a match, but it's less efficient).
        var name = method._name || 'constructor';

        // Method trying to access the variable must be in
        // the class prototype chain for protected.
        // For private, it must be in the prototype of the class
        // defining the var.
        if (name in proto && !proto.__lookupGetter__(name) && proto[name] === method &&
                (mayBeInSuperClass || proto.hasOwnProperty(name))) {
            return true;
        }
        return false;
    }

    function addPrivateOrProtectedProperty(proto, name, value, isPrivate) {

        // Doesn't work in IE as it doesn't support getters and setters.
        // Doesn't work on Safari or Opera < 10.10 (OK with Opera 10.5) as
        // getters and setters don't set the caller property on the function object.

        // When obj.name is accessed, this function will be called instead:
        proto.__defineGetter__(name, function guard() {
            if (methodInPrototype(isPrivate ? proto : this.constructor.prototype,
                    guard.caller, !isPrivate)) {
                return value;
            }
            else {
                throw "Error: cannot access " +
                    (isPrivate ? "private " : "protected ") + name + " property.";
            }
        });

        // When a method tries to write to obj.name, we define a new getter on the object
        // that returns the new value (after performing the same checks to maintain the
        // private/protected status), masking the getter set on the prototype of the object.
        proto.__defineSetter__(name, function guard(value) {
            if (methodInPrototype(isPrivate ? proto : this.constructor.prototype,
                    guard.caller, !isPrivate)) {

                // Define a new getter with closure over the value supplied as an argument
                // to the setter. Note the new getter is defined on the object *instance*
                // not the prototype (like the one above), so writing to one instance of
                // an object won't affect the properties on another.
                this.__defineGetter__(name, function guard() {
                    if (methodInPrototype(isPrivate ? proto : this.constructor.prototype,
                            guard.caller, !isPrivate)) {
                        return value;
                    }
                    else {
                        throw "Error: cannot access " +
                            (isPrivate ? "private " : "protected ") + name + " property.";
                    }
                });
            }
            else {
                throw "Error: cannot access " +
                    (isPrivate ? "private " : "protected ") + name + " property.";
            }
        });
    }

    Class.prototype.implement = function(key, value, doNotOverride) {

        if (typeof key === 'object') {
            for (var prop in key) {
                this.implement(prop, key[prop], value);
            }
            return this;
        }

        var proto = this.prototype;

        // Split name, e.g. 'protected methodName' -> ['protected', 'methodName']
        var tokens = key.split(' ');
        // Actual property name is final object in array.
        var name = tokens.pop();

        // Check if it already exists
        if (doNotOverride && name in proto) {
            return this;
        }

        // If there's a visibility modifier it (should)
        // be the next (and indeed only) object in the array
        var visibility = tokens.pop();

        // Record the method name as a property on the
        // function object to allow super functions to be called.
        if (value instanceof Function) {
            value._name = name;
        }

        // And actually add the property
        var isPrivate = (visibility === 'private');
        var isProtected = (visibility === 'protected');
        if (isPrivate || isProtected) {
            addPrivateOrProtectedProperty(proto, name, value, isPrivate);
        }
        else {
            proto[name] = value;
        }
        return this;
    };

    return Class;

})(); // End Class closure

Now, compatibility. As shown above, the code works in Firefox (at last v3.0+, possibly v2+), Chrome (at least the latest version, I've not tested on earlier ones) and Opera 10.5. Notably missing are of course IE and Safari. The reason for this is that two non-standard constructs are being used. First up is the use of a functions’s caller property; whilst not a part of the ECMA standard it nevertheless has been supported by every major browser (even IE) for some time. The other interesting construct is the use of getters and setters. Actually these have now been standardised, but the new syntax defined in the standard (Object.defineProperty) is currently only supported by IE8 and then only for DOM elements, which is useless in this context. All the other major browsers support the syntax used above though, even though it is not the one standardised, so why the lack of support from Safari and Opera pre 10.5? The answer is that these browsers fail to set the caller property on the getter/setter functions. I'd say this is a bug, but given it's the interaction between two non-standardised attributes that’s not really fair!

So is this all useless? No, not really. First of all, the technique could be applied without getters and setters to support private and protected methods (but not fields) by just replacing the getter with a standard function wrapper; this is still better than the solutions we have today as it avoids the callback problem but you won’t get the additional getter/setter benefit of preventing outside functions overwriting private/protected fields. However, over the next few years we should see browsers standardise the getter/setter syntax, leaving just the caller property to worry about. If that can get incorporated into the standard too, we could be on to a winner.

Update: Thanks to Tobie Langel for reminding me to point out that the caller property of functions not only isn’t standardised, but in ECMAScript 5 strict mode is an explicit error. I have yet to see a convincing explanation as to why this property is a security hole (which is the reason always given for its removal). The functionality is not replaceable with other JavaScript constructs (unlike arguments.callee which can be replaced by naming anonymous functions) and is so useful, I hope that it may make a reappearance in the spec as a valid property. Given the widespread support in browsers, it is a de-facto standard and unlikely to disappear soon, although I can understand people wanting to avoid it. I am not saying that this is the ultimate solution to information hiding in JavaScript, but it is an interesting approach and hopefully by providing examples such as this of useful features which cannot be implemented any other way, the standards committee may reconsider the caller issue; it has already been removed and resurrected once in Mozilla. To quote Brendan Eich (creator of JavaScript) from 2001: “All traces of a caller property were removed a while ago, to follow ECMA-262 and to avoid any chance of a security exploit. The removal seems to me to have been overzealous, because it axed the caller property of function objects *and* the much-more-exploitable caller (or __caller__) property of activation objects.”

Another update: and just for the hell of it, I've adapted the code to not use the caller property: see this post. All of the same features described above are supported, along with Safari and Opera 10.10.