patternjavascriptMinor
Issues with this pattern to restrict access to private members?
Viewed 0 times
thisissueswithprivateaccessmembersrestrictpattern
Problem
Since the original post, I have explored further regarding this pattern. In an effort to fix one self-percieved flaw (the lack of ability of prototype methods to access private object members), I have revised this Class pattern. As no one has responded regarding the pattern, I am offering a bounty.
In general, only people with a high level of expertise in JavaScript will probably be able to review this to my requirements. To be clear, here are my requirements:
Please, keep in mind that this is an abstract pattern. The pattern works, though the code utilizes pseudocode. Code Edit Class private member privateAccess was changed to this.privateAccess to allow for proper inheritance. There will be another edit to account for exploits of this.
```
[var | Namespace.]ClassName = (function(Class) {
/ Any static(class-wide) privates go here /
var privateMethod = function() {
return 'I\'m doing something privately';
};
/ The class prototype /
Class.prototype = {}; // or new BaseClass();
Class.prototype.propertyName = function(value) {
var returnValue;
// Unlock private Access
this.privateAccess = true;
// Forward the property access
returnValue = this.privateAccessFunction('propertyName', value);
// Lock it back up again!
delete this.privateAccess;
return returnValue;
};
// Lock the funct
In general, only people with a high level of expertise in JavaScript will probably be able to review this to my requirements. To be clear, here are my requirements:
- I am not looking for a review that utilizes convention as its primary argument.
- Code style should only be a factor, if and only if it can keep the pattern intact:
- All scopes must maintain their accessibility to the other scopes.
- Clear and Concise review regarding potential security issues.
- Verifiable performance statistics (if this is one of your review's arguments).
- Supplemental code and reasoning (for instance, where one would use Object.freeze() or places to consider validation).
- Other patterns to consider, if they can meet the scoping guidelines.
Please, keep in mind that this is an abstract pattern. The pattern works, though the code utilizes pseudocode. Code Edit Class private member privateAccess was changed to this.privateAccess to allow for proper inheritance. There will be another edit to account for exploits of this.
```
[var | Namespace.]ClassName = (function(Class) {
/ Any static(class-wide) privates go here /
var privateMethod = function() {
return 'I\'m doing something privately';
};
/ The class prototype /
Class.prototype = {}; // or new BaseClass();
Class.prototype.propertyName = function(value) {
var returnValue;
// Unlock private Access
this.privateAccess = true;
// Forward the property access
returnValue = this.privateAccessFunction('propertyName', value);
// Lock it back up again!
delete this.privateAccess;
return returnValue;
};
// Lock the funct
Solution
Regarding Code
Some changes can actually substantially improve the performance and usability of the code while keeping the requested features intact.
-
Get rid of the ClassName = ClassName || function(){}. This isn't actually necessary and limits the pattern. Instead, to keep your desired features, just stick the constructor.
-
Removing the
-
I suggest renaming
-
If you are providing private access to a Class's prototype, the capability should be custom built for each class and not inherited. I would move the
-
If you want a more generalized pattern, you might weigh the option of passing
-
To generalize even better with more added benefit, add
-
Get rid of the Object.defineProperty. Its not needed to limit the access to the private members. The
Result after Suggestions
After these modifications, the pattern may now be used with a
with an additional option
Regarding Performance
I have built a series of performance tests comparing this "Modular Class Pattern" with both the Prototypal and typical Constructor class structures. I considered comparing with the "Closure Classes" from OO.js but they are so similar that to do so would merely be a curiosity. The performance tests are quite extensive. You may look at the jsPerf here.
The statistics are quite interesting, especially across browsers. The most significant point of interest here is that you can hoist any method and in most cases get member access that rivals Prototype methods and Constructor methods. Static methods can show substantially more performance. Because of this pattern, it must be noted that the only thing that differentiates a static method from a hoisted object method is whether or not the method uses
Object creation is also interesting because it is significantly higher performing than Constructor, but only 1/2 as good (read the test notes) as defining via pure-prototype. Overall, you'll be able to see that the pattern generally performs well across implementation choices. One thing of note: things get slower when calling prototype methods internally from the constructor or constructor members from the prototype. (Both access hoisted methods with similar statistics) Performance drops even more when accessing private object members from outside of the constructor.
In other words, while the other features you require may provide substan
Some changes can actually substantially improve the performance and usability of the code while keeping the requested features intact.
-
Get rid of the ClassName = ClassName || function(){}. This isn't actually necessary and limits the pattern. Instead, to keep your desired features, just stick the constructor.
-
Removing the
this instanceof ClassName check from the passed function should be a serious consideration. This is already performed in the static instance method. This will dramatically improve performance and allow for additional capability. The only reason to keep that particular instanceof check is if you would like to Class to have specifically different behavior when a new object is not requested.-
I suggest renaming
instance() to create(). This is a more descriptive and semantic name. Additionally, if modified to allow for static creation (without new), it will fit both purposes.-
If you are providing private access to a Class's prototype, the capability should be custom built for each class and not inherited. I would move the
privateAccess variable back to the static level.-
If you want a more generalized pattern, you might weigh the option of passing
arguments (though an es5-shim might be required) or individually passing the argument names. The 1st option is more general and accessible. The 2nd performs faster.-
To generalize even better with more added benefit, add
Class.prototype.class = Class; change the ClassName.instance.call to this.class.create.apply(this, [argument method here]).-
Get rid of the Object.defineProperty. Its not needed to limit the access to the private members. The
privateAccess does that. Even if the method is somehow changed, it will no longer have access to the private members it is exposing. Result after Suggestions
[var ClassName = |Namespace.ClassName = |property: ]
(function(Class) {
var privateAccess = false;
/* Any static(class-wide) privates go here */
var privateMethod = function() {
return 'I\'m doing something privately';
};
/* The class prototype */
Class.prototype = {}; // or new BaseClass();
Class.prototype.propertyName = function(value) {
var returnValue;
privateAccess = true;
returnValue = this.requestPrivate('propertyName', value);
privateAccess = false;
return returnValue;
};
/* The class constructor */
Class.create = function(config) {
var myPrivateMembers = {
propertyName: 'value'
};
function requestPrivate(name, value) {
if (privateAccess && name !== undefined && typeof name === 'string') {
if (value !== undefined) {
myPrivateMembers[name] = value;
return this;
}
return myPrivateMembers[name];
}
return undefined;
}
if (this instanceof Class) {
// Do your object setup here
}
else {
return new Class(arguments);
}
};
return Class;
}(function(){return this.class.create.apply(this, arguments);}));After these modifications, the pattern may now be used with a
var, Namespace/Module or even as part of an object expression with no further changes except implementation choices. Instantiation occurs as before:new ClassName(args);with an additional option
ClassName.create(args);Regarding Performance
I have built a series of performance tests comparing this "Modular Class Pattern" with both the Prototypal and typical Constructor class structures. I considered comparing with the "Closure Classes" from OO.js but they are so similar that to do so would merely be a curiosity. The performance tests are quite extensive. You may look at the jsPerf here.
The statistics are quite interesting, especially across browsers. The most significant point of interest here is that you can hoist any method and in most cases get member access that rivals Prototype methods and Constructor methods. Static methods can show substantially more performance. Because of this pattern, it must be noted that the only thing that differentiates a static method from a hoisted object method is whether or not the method uses
this.Object creation is also interesting because it is significantly higher performing than Constructor, but only 1/2 as good (read the test notes) as defining via pure-prototype. Overall, you'll be able to see that the pattern generally performs well across implementation choices. One thing of note: things get slower when calling prototype methods internally from the constructor or constructor members from the prototype. (Both access hoisted methods with similar statistics) Performance drops even more when accessing private object members from outside of the constructor.
In other words, while the other features you require may provide substan
Code Snippets
[var ClassName = |Namespace.ClassName = |property: ]
(function(Class) {
var privateAccess = false;
/* Any static(class-wide) privates go here */
var privateMethod = function() {
return 'I\'m doing something privately';
};
/* The class prototype */
Class.prototype = {}; // or new BaseClass();
Class.prototype.propertyName = function(value) {
var returnValue;
privateAccess = true;
returnValue = this.requestPrivate('propertyName', value);
privateAccess = false;
return returnValue;
};
/* The class constructor */
Class.create = function(config) {
var myPrivateMembers = {
propertyName: 'value'
};
function requestPrivate(name, value) {
if (privateAccess && name !== undefined && typeof name === 'string') {
if (value !== undefined) {
myPrivateMembers[name] = value;
return this;
}
return myPrivateMembers[name];
}
return undefined;
}
if (this instanceof Class) {
// Do your object setup here
}
else {
return new Class(arguments);
}
};
return Class;
}(function(){return this.class.create.apply(this, arguments);}));new ClassName(args);ClassName.create(args);Context
StackExchange Code Review Q#33807, answer score: 3
Revisions (0)
No revisions yet.