It is with much happiness that I think I can finally say, without seeming like a fool, that: “JavaScript Getters and Setters are now prevalent enough to become of actual interest to JavaScript developers.” Wow, I’ve been waiting a long time to be able to say that.
I want to start by giving a whirlwind tour of Getters and Setters and why they’re useful. Followed by a look into what platforms now support Getters and Setters as to make them relevant.
Getters and Setters
Getters and Setters allow you to build useful shortcuts for accessing and mutating data within an object. Generally, this can be seen as an alternative to having two functions with an object that are used to get and set a value, like so:
{ getValue: function(){ return this._value; }, setValue: function(val){ this._value = val; } }
The obvious advantage to writing JavaScript in this manner is that you can use it obscure values that you don’t want the user to directly access. A final result looking something like the following (using a closure to store the value of a newly constructed Field):
function Field(val){ var value = val; this.getValue = function(){ return value; }; this.setValue = function(val){ value = val; }; }
Some example results:
var field = new Field("test"); field.value // => undefined field.setValue("test2") field.getValue() // => "test2"
Now, centered around this concept, is where getters and setters come into play. They allow you to bind special functions to an object that look like normal object properties, but actually execute hidden functions instead. The end result looks something like this:
var field = new Field("test"); field.value // => test field.value = "test2"; field.value // => "test2"
So let’s look at how you would go about setting something like that up. Mimicking the “hidden value property” style of before, our code would look something like this:
function Field(val){ var value = val; this.__defineGetter__("value", function(){ return value; }); this.__defineSetter__("value", function(val){ value = val; }); }
Now, if we wanted to, instead, define getters and setters within the context of our object prototype (and where having “private” data is less of a concern) we can then use an alternative object syntax for that.
function Field(val){ this.value = val; } Field.prototype = { get value(){ return this._value; }, set value(val){ this._value = val; } };
The syntax for getters and setters is typically what scare people the most about the feature. But after a little bit of use, it’s easy to get over.
Here’s another example, allowing a user to access an array of usernames (but denying them access to the original, underlying user objects.
function Site(users){ this.__defineGetter__("users", function(){ // JS 1.6 Array map() return users.map(function(user){ return user.name; }); }); }
As a bonus, here’s a method that I’ve written that can help you to extend one object with another (a common JavaScript operation) while still taking into account getters and setters:
// Helper method for extending one object with another function extend(a,b) { for ( var i in b ) { var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i); if ( g || s ) { if ( g ) a.__defineGetter__(i, g); if ( s ) a.__defineSetter__(i, s); } else a[i] = b[i]; } return a; }
This code is from the Server-Side Browser Environment that I wrote about a week ago. You’ll notice that, when looking through the code that I make liberal use of getters and setters to create this mock environment.
Additionally, in my custom extend() method you’ll notice two new methods: __lookupGetter__ and __lookupSetter__. These are immensely useful, once you start dealing with getters and setters.
For example, when I did my first pass at writing an extend() method, I started getting all sorts of errors – I was thoroughly confused. That’s when I realized that two things were happening with the simple statement: a[i] = b[i];
If a setter existed in object a, named i, and a getter existed in object b, named i, a[i]’s value was being set not to the other setter function, but to the computed value from b’s getter function. The two __lookup*__ methods allow you to access the original functions used for the methods (thus allowing you to write an effective extend method, for example).
A couple things to remember:
- You can only have one getter or setter per name, on an object. (So you can have both one value getter and one value setter, but not two ‘value’ getters.)
- The only way to delete a getter or setter is to do: ‘delete object[name];’ Be aware, that this command is capable of deleting normal properties, getters and setters. (So if all you want to do is remove a setter you need to backup the getter and restore it afterwards.)
- If you use __defineGetter__ or __defineSetter__ it will silently overwrite any previous named getter or setter – or even property – of the same name.
Platforms
Now, here is where things get really interesting. Up until just recently, talking about actually using getters and setters (outside of the context of Firefox/Gecko) was pretty much not happening.
However, look at this line up:
Browsers
- Firefox
- Safari 3+ (Brand New)
- Opera 9.5 (Coming Soon)
I used the following snippet to test browsers:
javascript:foo={get test(){ return "foo"; }};alert(foo.test);
Additionally, the following JavaScript environments support Getters and Setters
- SpiderMonkey
- Rhino 1.6R6 (New)
And as do the following ECMAScript implementations
- ActionScript
- JScript.NET 8.0
Mark Holton (July 18, 2007 at 5:02 pm)
…always prescient. Thanks, John.
Dan Webb (July 18, 2007 at 5:19 pm)
John, am I right in thinking that you can wildcard getters an setters some how so that code is called for any property being get/set?
I’m sure I saw this somewhere but can’t find any info on it…it would be incredible but may it was just a happy but very geeky dream :)
Dan Webb (July 18, 2007 at 5:57 pm)
Ah, it seems like I saw it in a presentation by Brendan Eich and that ‘catch-alls’ are a proposal for ECMAScript 2.0 and not implemented as yet. Oh well.
Karl G (July 18, 2007 at 10:57 pm)
Encapsulation is the boring part of getters/setters. The awesome part is that you can implement cells [1] without having to deal with annoying get()/set() calls.
[1] http://pycells.pdxcb.net/weblog/2006/07/12/what-are-cells/
attila szabo (July 27, 2007 at 9:56 am)
Getters and setters are really useful in terms of encapsulation.
The problem is that JS (many browsers) does not support them natively as for instance C#. Implementing your own getters and setters is not a good idea as it is rather slow.
http://w3net.eu/code/privateMembers/
Which version of EcmaScript were getters first introduced in?
Johan Sundström (August 22, 2007 at 5:04 am)
But getters and setters can’t be used by an extension wanting to give content level javascript a neat read/write access interface (rather than a function calling interface) due to some kind of security ban?
I’ve tried extending Greasemonkey to provide a GM_headers property whose value would be computed only upon reading it, like this:
var safeWin = new XPCNativeWrapper(unsafeContentWin);
var sandbox = new Components.utils.Sandbox(safeWin);
// ...
sandbox.__proto__ = {
get GM_headers() {
return headers.get();
}
};
sandbox.__proto__.__proto__ = safeWin;
…but unfortunately it hits a security error, throwing the exception “Permission denied to get property Sandbox.GM_headers” on trying to read it from the greasemonkey script running in the sandbox. Just not a viable route to go for some reason, or am I missing some details to mark the API as safe for consumption, grant content read permission, or similar?
Tercüme bürosu (October 28, 2007 at 9:18 pm)
always prescient. Thank you, John