Implement private variables for classes
Reported by Will Palmer | June 8th, 2008 @ 01:59 AM
Class.create() is a much prettier way to define things, but unfortunately the way it's set up makes declaring "private" variables impossible. Maybe I'm just idealistic? I gave it some thought, and here's what I came up with:
If the first argument to a method is named $private, instance-local private variables will be passed in automatically. The method itself is curried, so this is only at define-time that you need to worry about it.
SomeClass.addMethods({
foo: function($private) {
return 'I am a public method, but I reveal: ' + $private.private_method();
}
});
Private methods/properties can be added directly using Object.extend($private, {}), or through the class methods:
SomeClass.addPrivateProperties({});
and
SomeClass.addPrivateMethods({});
The distinction between the two is that functions passed into addPrivateProperties will be treated as initializers for the property values, and will be automatically called just before the initialize() method, passing in the arguments which the initializer will receive (and of course $private, if requested)
private methods / properties are inherited as would be expected, as are methods referencing $private.
This has been tested non-thoroughly by me, but does seem to work without error in firefox at least. I think it could do with some feedback regarding its utility and interface before polish is added.
More than anything, I'm looking for feedback as to whether this is even a good idea / how it can be improved. (new variable names are definitely needed.. one place has three hashes in a row named variations of private_properties). It may also want optimizations regarding speed, as .argumentNames() is called a lot.
patch against b1dd9357a8b08b3e730cc4911cd79a745d22597d
Comments and changes to this ticket
-
Juriy Zaytsev June 8th, 2008 @ 03:15 AM
- → State changed from new to enhancement
Privatizing instance and "class methods" is actually quite possible with a simple closure : )
var Foo = Class.create((function(){ ... // private members go here ... return { initialize: function(name) { // and they are accessible in instance methods through the closure }, // ... } })());Couple of notes:
1) I'm concerned about performance - storing private methods in a closure ("around" instance methods as shown above) is not too verbose and brings no speed/size overhead.
2) They are not really "private". One could still read them (though not modify as it looks like they are early bound with #curry)
3) How does $private being first argument play with $super being first argument?
4) Would be helpful to have some comments in the code : )
Cheers,
kangax
-
Juriy Zaytsev June 8th, 2008 @ 03:21 AM
It looks like it's possible to modify private methods:
var Foo = Class.create({ initialize: function(name) { // }, getSecret: function($private) { return $private.secret(); } }) Foo.addPrivateMethods({ secret: function(){ return 'it\'s a secret, don\'t you get it?' } }) Foo.private_instance_properties.secret.value = function(){ return 'spoofed'; }; new Foo().getSecret(); // "spoofed" -

Will Palmer June 8th, 2008 @ 11:49 AM
In response to your comments, in order:
(closure in initial creation)
Unfortunately, this would get rid of the prettiness benefits of using Class.create / SomeClass.addMethods(), as methods added later on would still not be able to access the private members. Still, point taken: my changes aren't necessary for the effect.
(interaction with $super)
I can only assume it doesn't play nicely. I couldn't figure out what that one was actually doing, so I couldn't make sure it was compatible. (my assumption is it is not)
(modification of private initializers)
I don't see this as a problem, as you can only modify them for the class. itself, not for instances. This allows the class to remain flexible, while keeping the values in the instance private.
In short: I don't really see a distinction between being able to call Foo.addPrivateMethods() at any time, and being able to do so at any time through a lower-level.
In the mean-time, I'll just use the closure method unless there's more interest in this way.
-

Will Palmer June 8th, 2008 @ 11:50 AM
Oh yes, and the lack of comments is due to my not seeing any in prototype to begin with - I expected when I got the git repository, there would be comments everywhere which got automatically stripped out when doing a rake dist. But there are no comments to be found! What's up with that? :/
-

Will Palmer June 8th, 2008 @ 12:30 PM
And now I've come to realize that the closure-method is not instance-local:
var Foo = Class.create((function(){ var private_member; return { initialize: function(name) { private_member = 'foo'; }, get_private: function() { return private_member; }, set_private: function(new_val) { private_member = new_val; } } })()); foo = new Foo(); document.write('foo: ' + foo.get_private() + '<br />'); foo.set_private('bar'); document.write('[set foo to "bar"]<br />'); document.write('foo: ' + foo.get_private() + '<br />'); //bar bar = new Foo(); document.write('[bar = new Foo()]<br />'); document.write('foo: ' + foo.get_private() + '<br />'); //foo document.write('bar: ' + bar.get_private() + '<br />'); //foo bar.set_private('baz'); document.write('[set bar to "baz"]<br />'); document.write('bar: ' + bar.get_private() + '<br />'); //baz document.write('foo: ' + foo.get_private() + '<br />'); //bazis there something I'm overlooking?
-
Juriy Zaytsev June 8th, 2008 @ 06:33 PM
Will,
unfortunately, when a closure is associated with constructor's prototype, it's obviously shared between all instance members. That means that private members act as "static", not instance (local).
To make private instance members, one would need to define them in constructor (not in constructor's prototype) as direct properties. That obviously leads to performance decrease, as every instance creates and carries its private instance methods (rather than sharing them via a prototype chain).
var Person = Class.create({ initialize: function(name) { // private instance property var name = name; // private instance methods this.getName = function(){ return name }; this.setName = function(value){ name = value }; } }); var foo = new Person(); foo.name; // undefined (truly private) foo.setName('Joe'); foo.getName(); // 'Joe' var bar = new Person(); bar.setName('John'); bar.getName(); // 'John' foo.getName(); // 'Joe' -

Will Palmer June 8th, 2008 @ 11:18 PM
Alright, that's as I thought. So basically the long and short of it is: You can't use prototype's prettier-ways of defining methods if you want private members, you have to do it the old-fashioned way (with no inheritence, etc)
So then this patch is /not/ re-doing things which are already possible, though it may be doing them in ways which are convoluted, slow, and not worth it.
Might this be able to be cleaned up in such a way that it adds little to no overhead (at least unless its features are actually used?) I'll probably keep working at it.
Please Login or create a free account to add a new comment.
You can update this ticket by sending an email to from your email client. (help)
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
The Prototype JavaScript library.
