I was reading an excellent report on ‘JavaScript hijacking’ [PDF] when I noticed, in one of their PoCs, what looked like an error. Here’s what I’m talking about:
function Object() { this.email setter = captureObject; } |
What this very clever piece of code does is define a setter for any ’email’ property of any created object. So when I create a new object and define an ’email’ property the ‘captureObject’ function will decide what happens. This code was taken from a PoC within the PDF demonstrating how one would ‘hack’ _protected_ JSON data.
What the code does is not what impresses me. It’s the fact that JavaScript allows you to define setters and getters which is awesome. I never even realised it was possible and to be honest didn’t even consider it a useful feature until I started playing around with it.
The problem
If we take jQuery as an example, when you want to retrieve the value of an input field you use the ‘val’ method, demonstrated here:
$('input[name=username]').val(); // => James |
By adding an argument to the method we can set a new value:
$('input[name=username]').val('JAMES1990'); |
Although jQuery may seem to have all the qualities of a good implementation (terseness, speed etc.) it’s not all that semantic. When you set a value in JavaScript (or any programming language) it makes more sense to have access to a property and then assign it the value rather than having to pass a string to a function. Alternatively you could have a ‘setter’ function named appropriately (e.g. ‘setValue’) and then you would pass the new value to that – and explicit naming convention like this seems more intuitive, however it’s not totally inline with what I mean by ‘semantic’.
Similarly when you retrieve a value you shouldn’t have to add those parenthesis; everything being ideal, it should be accessible as a property. The plain old DOM API allows us to retrieve input values in this fashion:
inputElement.value; // => Returns the value inputElement.value = 'whatever'; // => Sets the value |
The solution
Wouldn’t it be nice if we were able to tie in this semantic superiority with jQuery’s approach? Thanks to the awesome’ness of ‘getters’ and ‘setters’ we can! Consider this:
$('input[name=firstname]').value; // Returns the value. $('input[name=firstname]').value = 'whatever'; // Set's the value. |
The above shouldn’t really be possible (as far as I’m concerned) because the ‘value’ property would have to be continually updated in order for it to be equal to the current real value of the element in question. Although, due to recent discoveries it appears it is possible:
$.fn.__defineGetter__('value', function(){return $(this).val();}); $.fn.__defineSetter__('value', function(v){return $(this).val(v);}); // Now this works! - $('input[name=username]').value; // Returns the value. $('input[name=username]').value = 'whatever'; // Set's the value. // We're using jQuery's val() method to get the value // Yes, you could just use elem.value but that's not the point! |
I find this really cool because it means whenever you try to access or ‘get’ the value of the ‘value’ property the function defined on the first line above will be run (even though you’re not intentionally calling it).
Note that this only works in the newer version of non-IE browsers.
Other possibilities
There’s something sinister about it all though; the idea that a function is implicitly tied to the getter or setter of any property is somewhat concerning. There are some pretty cool possibilities though:
// Need a globally accessible random number generator: window.__defineGetter__('RANDOM', function(){return Math.random();}); // Whenever you need a random number: RANDOM; // => 0.4255734307570618 // Or perhaps the time? window.__defineGetter__('TIME', function(){return +new Date();}); // Returns the real time every time it's accessed, so: TIME; // => 1235901065883 // do stuff... TIME; // => 1235901065889 // do stuff... TIME; // => 1235901065894 // (Notice that TIME is never the same) |
Because we’re only defining ‘getters’ for these global variables it’s not possible to re-define them using normal notation:
RANDOM = 1; // Throws error, "setting a property that has only a getter" |
For me, this opens up an entirely separate realm of control. I very much like the idea of getters and setters. It means we no longer have to succumb to compromises like Obj.setSomething('something')
.
And… Aliasing!
Before I finish I want to mention one more awesome thing that this enables. Say, for example, you want a more concise way of retrieving the ‘parentNode’ of any particular element. You want it to be accessible via a shorter property name inherent of all elements. Something like this:
// Instead of this: myElement.parentNode; // You want this: myElement.pn; |
To achieve this we can create a basic ‘alias’ function which will make one property accessible via another:
function alias(obj, longcut, shortcut) { obj.__defineGetter__(shortcut, function(){return this[longcut]}); obj.__defineSetter__(shortcut, function(v){this[longcut] = v;}); } // Usage: alias(Element.prototype, 'parentNode', 'pn'); myElement.pn === myElement.parentNode; // TRUE // Works on all elements now: document.getElementsByTagName('div')[0].pn; // Body |
More info
- John Resig on ‘JavaScript getters and setters’
- MDC – ‘Defining getters and setters’
- Ajaxian – Getters and Setters in JS
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
I agree with what you say about jQuery, your method makes much more sense!
Very interesting…too bad it is not x-browser…damn IE.
Another great tip from James, thanks so much man! I find your blog constantly be full of great tips/howto’s!