A while back many people responded to this post, helping me see that my solution for providing private data access to prototypal functions was incomplete. Since then, I had been playing around with some possible solutions in my head and finally came up with two:
- Prototypal Registry
- Accessor Verification
Prototypal Registry
Prototypal Registry involves using an associative array of all of the original prototypal functions to make sure that the function that is calling the private data accessor function is authentic. The following source code is an example of a Person class which implements this solution to allow three prototypal functions access to private data:
(function(global) {
// Get generic hasOwnProperty function just in case it is overwritten.
var hasOwnProperty = ({}).hasOwnProperty;
// Adds the properties defined in objWithExtensions to objToExtend.
function extend(objToExtend, objWithExtensions) {
for(var key in objWithExtensions) {
if(hasOwnProperty.call(objWithExtensions, key)) {
objToExtend[key] = objWithExtensions[key];
}
}
}
// Creates a Person class.
var PersonRegistry = {
getName : function() {
var pData = this._('getName');
return pData.firstName + ' ' + pData.lastName;
},
setFirstName : function(firstName) {
var pData = this._('setFirstName');
pData.firstName = firstName;
return this;
},
setLastName : function(lastName) {
var pData = this._('setLastName');
pData.lastName = lastName;
return this;
}
};
extend((global.Person = function(firstName, lastName) {
var pData = {
firstName : firstName,
lastName : lastName
};
function _(name) {
return PersonRegistry[name] == _.caller && pData;
}
this._ = _;
}).prototype, PersonRegistry);
})(this);
var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());
The problem with this first solution is the fact that it cannot be guaranteed to work in all environments. Strict mode, Opera 10, BESEN and Rhino 1.7 don’t support the function “caller
” property (better breakdown), thusly preventing this solution from working in those environments. Due to this issue, I started thinking about a different solution.
Accessor Verification
The 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 used by all other private data accessors. This then would give that rogue code access to the private data of instances of all such classes. Accessor Verification prevents rogue outside code from being able to do that. Using this scripting pattern, you create a function which is called to then verify that the private data accessor function hasn’t been tampered with. After the accessor function is verified, it is then called with the secret key (accessible only by code within the closure) to get access to the private data. The following source code is an example of a Person class which implements this solution to allow three prototypal functions access to private data:
(function(key, global) {
// Creates a private data accessor function.
function _(pData) {
return function(aKey) {
return aKey === key && pData;
};
}
// Private data accessor verifier. Verifies by making sure that the string
// version of the function looks normal and that the toString function hasn't
// been modified. NOTE: Verification can be duped if the rogue code replaces
// Function.prototype.toString before this closure executes.
function $(me) {
if(me._ + '' == _asString && me._.toString === _toString) {
return me._(key);
}
}
var _asString = _({}) + '', _toString = _.toString;
// Creates a Person class.
var PersonPrototype = (global.Person = function(firstName, lastName) {
this._ = _({
firstName : firstName,
lastName : lastName
});
}).prototype;
PersonPrototype.getName = function() {
var pData = $(this);
return pData.firstName + ' ' + pData.lastName;
};
PersonPrototype.setFirstName = function(firstName) {
var pData = $(this);
pData.firstName = firstName;
return this;
};
PersonPrototype.setLastName = function(lastName) {
var pData = $(this);
pData.lastName = lastName;
return this;
};
})({}, this);
var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());
The above solution should work in all JavaScript/JScript environments. If the private accessor function is overriden, the call by any prototypal function will fail because the verification function will notice that it has been tampered with and undefined
will be returned to the prototypal function as the private data container. Neither by using the previous line of thinking (suggested by Esailija), nor by using any other have I been able to extract private data without modifying the original code (that which appears in the closure). The only time when rogue code could dupe the verifier function is if the rogue code overrides Function.prototype.toString
before the closure defining the classes is executed. In that case, though, you may have more issues than just those encountered with your private data becoming public. 😀
I would be interested in seeing if any readers can find a way to get access to the private data object with code that runs after the class is defined. If you are good at JavaScript, and think you can figure it out, let me know, but I believe this is a rock solid solution. 8) Happy hacking!!!
3 Comments
ildar · April 1, 2013 at 6:44 AM
Good work! But it looses its usefulness because of impossibility to put it into a librarian file and use as a part of other different projects. You will have to copy and paste it from one project to another one (but you can use it as a part of some sort of prolog which can be prepended before the resulting assembled file, as an example. But not all use this way).
Is it really important to have true private properties in JS? Just come to naming convention (leading or trailing Understore character is enough to consider a property as private). I think this investigation is very good but more academical one and it doesn’t have practical profit.
Chris West · April 3, 2013 at 11:39 AM
I mostly agree with you Ildar. This seems unecessary in JavaScript because of the many implications of using the language. I really just wrote the post to help others to see that there is a solution to providing prototypal functions access to private data. This pattern could be used in JS libraries such as jPaq or others but I doubt I will use it because of the extra function calls.
JavaScript – Prototypes, Private Data, & Safe Factories | Chris West's Blog · April 3, 2013 at 2:43 AM
[…] ← Previous […]