something to do with web development

July 30, 2006

Private Instance Variables With Prototypal Inheritance

Some time ago, Douglas Crockford posted a thoughtful essay demonstrating use of closures to provide private members in JavaScript. However, when used with JavaScript's own prototypal inheritance model, Crockford's approach to private variables suffers: prototypal inheritance renders such private variables static for instances of the subclass. For Crockford's intentions, this wasn't an issue. In fact, Richard Cornford noted and exploited the effect to produce private static members. As well, Crockford had already demonstrated alternative inheritance methods, sometimes used as a workaround when private instance members are desired. I'm going to describe a much simpler alternative.

Let's see some example code first:

/*********************************
 * PERSON
 *********************************
 * Person is a base class.
 */
function Person() {
    // Private self-referential variable
    // for private methods
    var self = this;

    // Private member 'age'
    var age = null;

    // Privileged accessor for 'age'
    this.getAge = function() {
        return age;
    }

    // Privileged mutator for 'age'
    this.setAge = function(newAge) {
        if(newAge>0) {
            age = newAge;
            return true;
        }
        else {
            return false;
        };
    };

    // Private member 'ssn'
    var ssn = null;

    // Privileged accessor for 'ssn'
    this.getSsn = function() {
        return ssn;
    }

    // Privileged mutator for 'ssn'
    this.setSsn = function(newSsn) {
        if(newSsn.match(
            /[0-9]{3}\-?[0-9]{2}\-?[0-9]{4}/)) {
                ssn = newSsn.replace(/\-/g, '');
                return true;
        }
        else {
            return false;
        };
    };
};

// Public property 'name'
Person.prototype.name = '';

/*********************************
 * EMPLOYEE
 *********************************
 * Employee is a subclass of 
 * Person.
 */
Employee = function() {
    // Employee inherits Person's members
    Person.call(this);

    // Private self-referential variable
    // for private methods
    var self = this;

    // Private member 'salary'
    var salary = null;

    // Privileged accessor for 'salary'
    this.getSalary = function() {
        return salary;
    }

    // Privileged mutator for 'salary'
    this.setSalary = function(newSalary) {
        if(newSalary>18000) {
            salary = newSalary;
            return true;
        }
        else {
            return false;
        };
    };
};

// Employee inherits Person's prototype,
// but has its own constructor
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;

/*********************************
 * EMPLOYEES
 *********************************
 * EMPLOYEES is an Array employed
 * to hold employees.
 */
EMPLOYEES = [];

// Public method to add new employees
EMPLOYEES.add = function(name, ssn, age) {
    var employee = new Employee();
    employee.name = name;
    employee.setSsn(ssn);
    employee.setAge(age);
    return this.push(employee);
}

/*********************************
 * Finally, some code:
 */
EMPLOYEES.add('Bernard', '111-11-1111', 19);
EMPLOYEES.add('Hoagie', '222-22-2222', 19);
EMPLOYEES.add('Laverne', '333-33-3333', 21);

// Let's see what we know about Bernard
alert('Name: ' + EMPLOYEES[0].name + "\nSSN: " +
    EMPLOYEES[0].ssn /* this won't work! */ + 
    "\nAge: " + EMPLOYEES[0].getAge() );

// What's the deal with Hoagie
alert('Name: ' + EMPLOYEES[1].name + "\nSSN: " + 
    EMPLOYEES[1].getSsn() + 
    "\nAge: " + EMPLOYEES[1].getAge() );

// Laverne lied about her age
alert(EMPLOYEES[2].name + ' is ' + 
    EMPLOYEES[2].getAge() );

// Let's set her record straight
EMPLOYEES[2].age = 19;
alert(EMPLOYEES[2].name + ' is still ' + 
    EMPLOYEES[2].getAge() );

// Hey, that didn't work!
EMPLOYEES[2].setAge(19);
alert(EMPLOYEES[2].name + ' is now ' + 
    EMPLOYEES[2].getAge() );

Bernard, Hoagie, and Laverne; Chron-o-John-testing temporary employees of Dr. Fred Edison. As such, they enjoy the benefit of a salary which, much to Dr. Fred's vexation, has to meet a union minimum of $18,000. For the sake of argument, every Employee also happens to be a Person, and people have their own private properties: age and ssn (Social Security Number), both of which have accessors and validating mutators. Lastly, people have names. Note that Person::name is the only public property mentioned in our example.

So, Employee uses Person as a prototype. Normally, this would mean that a single instance of Person would become the shared prototype for all Employees—that's how prototypal inheritance works. However, note our crafty use of JavaScript's call method inside Person's constructor. To make sense of this, we have to stop thinking of Employee and Person as subclass and superclass. Person is being used as a prototype for Employee. Person is a constructor, but it is still a function that can be called just like any other. So, with Employee's constructor, we call Person. Thus, the private instance variables of Person will still be private instance variables, Person being called for each instance of Employee. Meanwhile, prototype.constructor allows for normal prototypal inheritance—including the calling of accessors and mutators within Employee.

You'll note we use an unusual term to describe the methods we have declared within constructor functions: privileged. As defined by Crockford, privileged methods, assigned using the this keyword, have access to private variables and methods, and are themselves accessible from public methods and external calls. The difference between a privileged method and a public method is that public methods cannot access private members.

I've also declared the variable self at the start of every constructor. This variable can be used to refer to privileged methods of the containing class inside the scope of private methods. To be honest, I had a hard time finding practical application for this, but thought I'd leave it in case it solves some future problem for anyone else. Here's a very unpractical usage example:

function Foo() {
    var self = this;
    var bar = null;

    // Privileged accessor for 'bar'
    this.getBar = function() {
        return bar;
    }

    // Privileged mutator for 'bar'
    this.setBar = function(value) {
        bar = value;
    }

    // Private method uses 'bar'
    function quantifyBar() {
        return self.getBar()/70;
    }

    // Privileged method uses 'quantifyBar'
    this.getBarPercent = function() {
        return quantifyBar()*100;
    }
}

One word of caution: when using any of these techniques, your private variables, private methods, and privileged methods will all become instance members. Prototypal inheritance purists may consider this overhead. Still: if you would like to use private members in superclass constructors, this is the simplest technique I've found for JavaScript 1.5.