If that title didn’t make any sense and you’re wondering if you came to the right place this will be about Javascript functions. I’ll touch on everything from how scope works with Javascript, to using it with objects and even bending it using call/apply. This is meant to be the beginning of an exploration of how Javascript can be an object oriented, class driven language even though at first its prototypal nature doesn’t seem to lend itself to it (with some Dojo Toolkit cheerleading along the way-because Dojo is awesome and for any who haven’t used it, it’s like JQuery on steroids. It supports classes and inheritance with its dojo.declare() object. To learn more look that up over at docs.dojotoolkit.org).
Before I get started I need to do a quick aside on firebug and the javascript console. If you already know all about it skip to the =======Done with the firebug aside===== line.
If you don’t know about it, firebug will make coding javascript much easier. It will also let you test my examples. So go to http://getfirebug.com/, install it on FireFox (the best browser to develop javascript in, that’s not just my opinion), and read a little about it. For this tutorial I’ll just use console.log() to show values returned by functions. If you read nothing else over there, at least glance at that.
=======Done with the firebug aside=======
Ok, now that’s out of the way lets start with functions. In javascript creating functions and calling them is as simple as:
1 2 3 4 5 6 7 | function createArray(a, b){ return [ this, a, b ]; } var myArray = createArray('testing', 'check'); console.log(myArray); // array members: window, "testing", and "check" print out in the console |
If you want to create a well designed web app, this is not so good. Why? Because you created and called the function in the global namespace (notice that “this”, the first member returned in the array came back as “window”).
Context is a big deal in javascript, and if you don’t supply the context Javascript supplies it for you (hence the window, global object). It picks up all your stray functions, like a giant comfortable bus would pick up homeless in a perfect world. That doesn’t mean it’s a loose global function though. Instead it’s a method of the window object, which means you can call the function like this:
1 2 3 4 5 6 7 8 | var myArray = window.createArray('testing', 'check'); console.log(myArray); // [ window, 'testing', 'check' ] console.log(typeof(window.nonExistantMethod)); //undefined console.log(typeof(window.createArray)); //function |
So if you want to avoid globals in Javascript (as you should), then you should start playing with objects. An object is simply something between curly braces ({}). So lets make an object to attach this function to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //First store the object as a var. This would be its namespace var arrayEr = { someProperty: 'a value of this object', ourFunction: 'createArray' } //Notice that in an object the value name is followed by a colon //and then the value in the object var test = arrayEr.ourFunction('testing','check'); console.log(test); //[ arrayEr, 'testing','check' ] //You could also call the function as if it were an array var test = arrayEr['ourFunction']('testing','check'); console.log(test); //[ arrayEr, 'testing','check' ] |
We made the object “arrayEr” and for its method ourFunction we wrote the string ‘createArray’. That made it so that when we called arrayEr.ourFunction() as a method it looked for the function named ‘createArray’ and called it. The cool thing here is it called it in its scope. Instead of window it was arrayEr. We did this as an exercise, the best way to define this sort of thing would actually be like this:
1 2 3 4 5 6 7 8 9 | var arrayEr = { someProperty: 'a value of this object', ourFunction: function(a, b){ return [ this, this.someProperty, a, b ]; } } var test = arrayEr.ourFunction('testing','whatever'); console.log(test); //[arrayEr, 'a value of this object', 'testing', 'whatever' ] |
Here instead of looking the function up by the name ‘createArray’ we have created it as a method named ‘ourFunction’. I added a little bit of code that would return the property of the object too, and it showed up in the returned array. Cool huh!
Scope can be a little tricky, so here’s an example that lets you get an idea of how it works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <input type="button" value="Button 1" id="btn1" />
<input type="button" value="Button 2" id="btn2" />
<input type="button" value="Button 3" id="btn3" onclick="buttonClicked();"/>
<script type="text/javascript">
function buttonClicked(){
var text = (this === window) ? 'window' : this.id;
alert( text );
}
var button1 = document.getElementById('btn1');
var button2 = document.getElementById('btn2');
button1.onclick = buttonClicked;
button2.onclick = function(){ buttonClicked(); };
</script> |
Click the first button and you get “btn1″ because it’s a method invocation and this will be assigned the owner object (the button input element). Click the second button and you get “window” because buttonClicked is being called directly (i.e. not like obj.buttonClicked()). Clicking the third gives the same result, because assigning the event handler in the element’s tag calls it directly like the second did.
Dojo has handy functions for event handling, like dojo.connect(). With dojo.connect() you can specify the scope you want, again for a better look at that docs.dojotoolkit.org is a good place to go.
This can happen because Javascript has two handy functions, call and apply. These let you call functions in different scopes than their own. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var Dog = { sound: "woof", talk: function(name, nature){ console.log(this.sound + ", "+this.sound+", my name is "+name+" "+nature); } } var Cat = { sound: "meow", talk: function(name, nature){ console.log(this.sound + ", "+this.sound+", my name is "+name+" "+nature); } } Dog.talk("fido","the dog"); //woof, woof, my name is fido the dog Cat.talk("felix","the cat"); //meow, meow, my name is felix the cat Cat.talk.call(Dog, "felix", "the cat"); //woof, woof, my name is felix the cat Dog.talk.apply(Cat, ["fido","the dog"]); //meow, meow, my name is fido the dog |
Pretty cool right? Call and apply require the new scope, then call takes the parameters as separate arguments while apply takes them as an array. That lets you call functions in different scopes! A Cat object can be made to “woof” while a Dog can be made to “meow”. This may not seem like a lot at first glance, but it is one of the most powerful features in Javascript and mastering it lets you do all kinds of awesome things.
Sure, go for it!