A while back I wrote this post about having classes with prototype functions that have access to private variables. To clarify this discussion, I have created this slideshow which briefly outlines how to accomplish writing JavaScript classes with private members which are accessible by prototype functions. In addition, the following is the JavaScript code that I wrote and tested in the JavaScript Box:


// Closure for defining the `Person` class.
(function(key) {
  // Define Person in global scope
  Person = function(firstName, lastName) {
    // Store private members in one object.
    var privateVars = {
      firstName : firstName,
      lastName : lastName
    };
    
    // Define a getter function that will only return private members
    // for privileged functions.
    this._ = function(aKey) {
      return aKey === key && privateVars;
    };
  };
  
  // Define a getter for the full name.
  Person.prototype.getName = function() {
    var _ = this._(key);
    return _.firstName + " " + _.lastName;
  };
  
  // Define a setter for the last name.
  Person.prototype.setLastName = function(newLastName) {
    this._(key).lastName = newLastName;
    return this;
  };
  
  // Define a getter for the first name.
  Person.prototype.getFirstName = function() {
    return this._(key).firstName;
  };
  
  // Create a `Male` class which inherits the Person prototype functions.
  Male = function() {
    Person.apply(this, arguments);
    this._(key).sex = "male";
  };
  Male.prototype = new Person;
  
  // Define a new `getSex()` function for this class.
  Male.prototype.getSex = function() {
    return this._(key).sex;
  };
  
})({});

// Create two people.
var resig = new Male("John", "Q."),
    crockford = new Person("Douglas", "Crockford");

// Show John's full name.
alert(resig.getName());

// Change John's last name and then show his full name.
alert(resig.setLastName("Resig").getName());

// Get Resig's sex
alert(resig.getName() + " is a " + resig.getSex());

// Show Douglas' first name.
alert(crockford.getFirstName());

If you look closely, you will notice that I even threw in some prototypal inheritance so that you can figure out how to make subclasses that use prototype functions to get access to private variables. I have to admit that this idea came from this post from JavaScript extraordinaire, Dean Edwards. Have fun!!! 8)


4 Comments

Chris West · March 15, 2013 at 10:02 PM

A few people were able to find a security flaw in the way I have my private data accessor function setup. Basically, the issue is that you can steal the key that is within the closure and then use it to get access to private data. Therefore I wrote the following which is a modification of the above code that prevents the key from being stolen:
[code language=javascript]
// Closure for defining the `Person` class.
(function(key) {
// Define Person in global scope
Person = function(firstName, lastName) {
// Store private members in one object.
var privateVars = {
firstName : firstName,
lastName : lastName
};

// Define a getter function that will only return private members
// for privileged functions.
this.$ = function(aKey) {
return aKey === key && privateVars;
};
};

// Define a getter for the full name.
Person.prototype.getName = function() {
var _ = this._();
return _.firstName + " " + _.lastName;
};

// Define a setter for the last name.
Person.prototype.setLastName = function(newLastName) {
this._().lastName = newLastName;
return this;
};

// Define a getter for the first name.
Person.prototype.getFirstName = function() {
return this._().firstName;
};

// A secure prototypal function that will make sure the getter hasn't been
// modified before using it.
Person.prototype._ = function() {
if (/w+===w+&&w+;?}/.test((this.$ + "").replace(/.*?{|s/g, ""))) {
return this.$(key);
}
};

// Create a `Male` class which inherits the Person prototype functions.
Male = function() {
Person.apply(this, arguments);
this._().sex = "male";
};
Male.prototype = new Person;

// Define a new `getSex()` function for this class.
Male.prototype.getSex = function() {
return this._().sex;
};

})({});

// Create two people.
var resig = new Male("John", "Q."),
crockford = new Person("Douglas", "Crockford");

// Show John's full name.
alert(resig.getName());

// Change John's last name and then show his full name.
alert(resig.setLastName("Resig").getName());

// Get Resig's sex
alert(resig.getName() + " is a " + resig.getSex());

// Show Douglas' first name.
alert(crockford.getFirstName());
[/code]

I guess it may also be important to make sure that the toString function for the _() isn’t overwritten. In the end, it probably isn’t worth it to make sure the key isn’t stolen but it can be done.

Daniel Cohen Gindi · March 18, 2014 at 6:10 PM

Please note that you are testing for function contents while function decompilation is not supported by all environments, and I’m not sure it’s even a written standard.

Also you are making way too much work just for private vars… Running a RegEx just to access a private var? Invoking function decompilation on the way? A simpler an faster way could be to hide the key *inside* the closure, not in the function signature, and use it internally.

But I wouldn’t be too concerned about “security” of “private variables” in ECMA anyway, as the language is not designed for this, and the code is only compiled to machine code in memory, so the users have access to the source code.

You should define your private vars by a convention that suits your environment. One that I like is _privateVar. Any subclass would not access such vars by convention, and everyone is happy.

    Chris West · March 19, 2014 at 12:10 AM

    I would use a closure but since we are trying to make this available to prototypal functions it wouldn’t work.
    I do agree that this is too much work to get true private variables but this is more of a proof that it is possible than an optimal solution.

JavaScript – Prototypal Functions & Private Data | Chris West's Blog · April 1, 2013 at 12:29 AM

[…] true issue with my original solution (from this post and this post) was the fact that you could override the private data accessor and steal the secret key that was […]

Leave a Reply to Chris West Cancel reply

Your email address will not be published. Required fields are marked *