Fun with Functions in JavaScript
Recently I noticed that the number of hours I am able to clock is increased when the work involves JavaScript. Formerly, I regarded it as the red-headed step child of the web and a language of pain. Now, I think of it as the language of the future. In the year two thouuuusaaandddd…
As I said in the last post, we have been doing a lot of JavaScript in the past month. In fact, Rails has become more of a means to route urls to JSON than anything else. Along the way, I’ve been picking up some subtleties that I thought I would share.
Functions as Classes
Ever seen this style of js before?
function KeyCode(event) { this.event = event; this.normalize = function() { return (this.event.keyCode ? this.event.keyCode : this.event.which); } this.isEnter = function() { return this.normalize() == 13; } }
I use to be repulsed by it. No, it was more than that. I hated it. Now, sadly, I have come to love it. If you haven’t came across this before, it is one way (of many) to create a class (as I would call it) in JavaScript.
KeyCode is the class and it has a public variable event and two public functions (normalize and isEnter). So how would you use something like this? Let’s say you have a text area for a chat input and you want it to submit when you press enter, instead of making a newline.
function onTextareaKeyDown(event) { if (new KeyCode(event).isEnter()) { $(this).parents('form').submit(); } } $('#chat textarea').keydown(onTextareaKeyDown);
I really like how that reads. It is immediately more apparent what the intent of this is than something like this:
function onTextareaKeyDown(event) { if ((event.keyCode || event.which) == 13) { $(this).parents('form').submit(); } } $('#chat textarea').keydown(onTextareaKeyDown);
Who is actually going to remember that 13 is the enter key a month, or even a week later? Plus, I can wrap up other key codes in the KeyCode class such as whether or not ctrl or command was pressed or if the key was esc.
Public vs. Private Functions
The other thing you may have picked up on is that I said public variable and public functions a few paragraphs above. What I mean by that is that once you create a new instance of the the KeyCode class, you have access to the event and to the normalize and isEnter functions outside the class.
var keycode = new KeyCode(event); keycode.isEnter() // would return true or false keycode.normalize() // would return keycode across browsers keycode.event // would equal the event argument used initializing KeyCode
Let’s say that you decide normalize and event should only be used internally and no other developers (including yourself) should actually use them on outside the KeyCode class. One way to do that would be like this:
function KeyCode(event) { function normalize() { return (event.keyCode ? event.keyCode : event.which); } this.isEnter = function() { return normalize() == 13; } }
event is automatically bound to be used inside the functions so all we did is remove storing it on the object (this.event = event). Similarly, we no longer store the normalize function on the object. The cool this is that now this function is only available with the KeyCode function. Now, if you created a new KeyCode instance, you would not be able to call event or normalize as in the previous example:
var keycode = new KeyCode(event); keycode.isEnter() // would return true or false keycode.normalize() // TypeError: Result of expression 'keycode.normalize' [undefined] is not a function. keycode.event // undefined
This is a great way to make helper functions that are specific to your class and don’t need to be exposed.
Functions as Callbacks
You know how jQuery and Prototype allow you to bind callbacks to events? I hate to break it to you, but this is really easy to do. Here is a really simple example that takes one callback.
function doWork(callback) { // do something that takes a while if (callback) callback(); }
That is really all there is to it. Then when you call the function, you can pass in an anonymous function or one you have predefined to be called when it is time.
doWork(function() { console.log('doing some work'); });
‘doing some work’ would get logged to the console when the work was done and the callback was fired. If you have something that could be successful or fail and you need to handle those differently, you can do something as easy as this.
function doWork(options) { // do something that takes a while if (success) { if (options.success) options.success(); } else { if (options.fail) options.fail(); } }
The reason I do the if statement before executing the callback in both of these examples is so the callback can be optional. If the callback is set, it will be fired and if not, it will be ignored. You can also pass arguments in when you fire the callback and if the callback function you used as your argument has them defined as parameters, you can easily pass things around.
Functions as Strings
Occasionally, I see case statements and such that define which method to call. Assume in the code below that message would be a JSON object like {action:‘enter’, body: ‘nunes entered the room’}.
var Room = { receiveMessage: function(room, message) { var html = ''; switch(message.action) { case 'enter': html = Room.enterHTML(message); break; case 'exit': html = Room.exitHTML(message); break; // etc, etc, etc. } room.append(html); }, enterHTML: function(message) { // return enter message html }, exitHTML: function(message) { // return exit message html } }
This case statement can be completely wiped out by doing something like this.
var Room = { receiveMessage: function(room, message) { var html = Room[message.action + 'HTML'](message); room.append(html); }, enterHTML: function(message) { // return enter message html }, exitHTML: function(message) { // return exit message html } }
Everything is an object. Objects can access properties using brackets. Room.enterHTML is the same thing as Room[‘enterHTML’]. They both return a pointer (as I think of them) to a the Room’s enterHTML function. All you need to do call a function using the bracket style is add parenthesis to the end, just like you would a normal function call. You can even pass in arguments this way, as I did with the message above.
Conclusion
Using some of the techniques I’ve shown above will clean up your code and make it more maintainable in the long run. These are a just a few of the things that I’ve noticed I’m using a lot. I’m sure there are more and I’ll keep posting them here as I find them. If you have any tried and true techniques, feel free to share in the comments below.