Saturday, September 29, 2007

My inner request for Outer !

If you read the preceding entry basics of easing and that your are not familiar with javascript, you were perhaps surprised with the following code concept:
function greetings(msg) {
        function display() {
            alert(msg);
        }
        display();
}
So what's going on there ?
We have a main function that receives a string as a parameter.
Nothing extraordinary here!
Then we define a function within the main function that will be in charge of alerting the parameter.
Here it's a little bit unusual!
In fact, you can define a function from within a function in javascript and you can nest the functions as will !
The inner function can access the global variables define within the scope of the outer function.
Why is it so cool will you ask?
Well, the cool thing about inner functions is that they will work even if the outer function as returned and this can be very useful for dealing with variable scoping.
You can write the above inner function with an anonymous function if you want:
function greetings(msg) {
        (function() {
            alert(msg);
        })();
}
This is an anonymous function as it does not have any name !
Enclosing an anonymous function with parentheses ()();, will cause the function to be executed right away !

Ok, this example is very simple and is not very useful, so let's see something more powerful.
If you do the following, what's going to happen ?

function myEvents(elm,events) {
   for(i=0;i < events.length;i++) {
      elm['on'+events[i]]=function () { 
           alert(i);
        }
   }
}
myEvents(document.getElementById('the_click'),['mouseover','mouseout']);
try me
if you try this function, mouseovering and mouseoutting (it's new) the html element will alert... 2 every time !
We could have expect 0 for mouseover and then 1 for mouseout but no,no.
Why then ?
This is a scoping problem;
myEvents function, the main function has elm, events and i as variables.
As I said, inner functions can access the variables of the outer functions but one important thing:
If you don't call the inner function during the loop,the function will accede the value of the variables once the outer function has returned !
That is why this outputs the last value of i and not the value in between.

So how can we output the right value ?
As this is a scoping problem, we need to scope the value of the variable to the scope of an inner function that we will call right away.
function myEvents(elm,events) {
   for(i=0;i < events.length;i++) {
      function display() {
      elm['on'+events[i]]=function () {
           var n=i; 
           alert(n);
        }
      }
      display();
   }
}
myEvents(document.getElementById('the_click'),['mouseover','mouseout']);
try me
Tadam !

It doesn't work !
If you have a closer look to the function above, we are trying to scope the value of i within the anonymous function applied to the object.
But this function is not executed right away, it will be executed when you will mouseover or mouseout on the link.Therefore, this function is calling the last value of the outer function.
We need to capture the value within an inner function that will be executed right away.
So...
function myEvents(elm,events) {
   for(i=0;i < events.length;i++) {
      function display() {
        var n=i; 
      elm['on'+events[i]]=function () {
           alert(n);
        }
      }
      display();
   }
}
myEvents(document.getElementById('the_click'),['mouseover','mouseout']);
try me
Tadam !

It works !
In this last function, we capture literally the value of the global i to the scope of the display function by putting it in the variable n.
While we have this value under the hat,in our scope, we execute the function that will refer to the value we want !
If writing and finding a name to this 'capturing' function is a pain, just short it up:
function myEvents2(elm,events) {
   for(i=0;i < events.length;i++) {
      (function () {
           var n=i;
      elm['on'+events[i]]=function () { 
           alert(n);
        }
      })();
   }
}

1 comment:

Marek said...

this is great explanation of JS scopes, thank you very much!