Question

jQuery iterator functions like each have syntax similar to this:

.each(function(index, element))

which would seem to imply that a function matching this declaration would have to take 2 parameters. Something like:

function my_func(index, element){
  alert(index+":"+element);
}

To me this gives 2 possible declarations:

$("li").each(my_func);

or

$("li").each(function(index, element) {alert(index+":"+element);});

The first one confuses me because I don't see index or element passed to my_func. Is there some magic in jQuery that knows that my_func takes 2 parameters that each is providing?

Secondly, if I had declared

var my_func= function(index, element){
  alert(index+":"+element);
}

would this change anything. My understanding is that the first is Declarative and the second is Expressive, but, in this case, they should behave the same?

Lastly, most each implementations that I see do something like this:

$("li").each(function(){alert(this)});

Are all the parameters optional to the each callback?

Was it helpful?

Solution

Ways to Pass a Function as a Callback

In this:

$("li").each(my_func);

You are just passing a reference to my_func to the .each() method. The each method itself will pass the two arguments to that function when it calls it. IF you want to access those two arguments, then you can declare them as arguments in your own definition of my_func, but you do not have to. The arguments will be there though whether you declare them or not.

You can declare my_func in a number of different ways:

// somewhat normal way and you can easily access the index and element arguments
function my_func(index, element) {
     // code here
}

// if you don't need index and element, you don't need to declare them
// but they are still actually there and can be reached via the arguments object
function my_func() {
     // code here
     console.log(arguments[0]);   // index
}

Declaring functions with var

The difference between:

var my_func= function(index, element){
  alert(index+":"+element);
}

and

function my_func(index, element){
  alert(index+":"+element);
}

is largely one of timing. In the second, my_func is defined as a function as soon as the javascript file it is located in is located and parsed and the scope it is defined in becomes live and is available "as if" it was declared at the very top of the scope in which it is defined. This is often called "hoisting" where the definition is hoisted to the top of the scope in which it is defined.

In the first, my_func is not a function UNTIL that line of JS is executed in the normal execution flow of that javascript file.

Other than the timing, you can pretty much use the two interchangeably. If personally prefer the function xxx() form because then I don't have to worry about whether the function is being called before it's declaration runs - but this is really a personal style preference.


Do You Have to Declare the Callback Arguments

To your last question, you only need to declare the arguments to your callback if you want to use them. In javascript, a function is the exact same function whether you declare it with arguments or not (not the case in languages like C++) so you only have to declare them if you want to access them by name.

Function argument declarations in javascript are optional. You can declare more than what is passed to the function and you can declare less than is passed to the function. Neither causes any sort of error in javascript.

Let's say you have this code:

 function callWithDelay(t, fn, arg1, arg2) {
     setTimeout(function() {
         fn(arg1, arg2);
     }, t);
 }

The expected way to use this would be to define a function that takes two arguments and pass it as the fn argument.

 function myFunc(msg, color) {
     var obj = document.getElementById("error")
     obj.innerHTML = msg;
     obj.style.color = color;
 }

 callWithDelay(2000, myFunc, "Both first and last name are required", "red");

But, you don't have to pass a function that has exactly those arguments. You could do it like this (accessing the arguments via the arguments object rather than by name):

 function myFunc() {
     var obj = document.getElementById("error")
     obj.innerHTML = arguments[0];
     obj.style.color = arguments[1];
 }

 callWithDelay(2000, myFunc, "Both first and last name are required", "red");

Or, you could pass a function that doesn't even need those arguments:

 function myFunc() {
     document.getElementById("error").style.display = "none";
 }

 callWithDelay(2000, myFunc);

The callWithDelay function doesn't care what kind of function reference you pass it. It can literally be any function. The callWithDelay function will pass two arguments to the callback that was passed to it when it calls it, but it's purely up to the receiving function whether it uses those arguments. In the last example, I left off the last two arguments to callWithDelay when it was called. When you leave off an argument (at the end of the call list), that argument is simply undefined. In javascript undefined is a legitimate value. So, when I leave them off the call to callWithDelay(2000, myFunc), they are undefined and thus undefined is what is passed to the myFunc callback as its two arguments. In this case, since those arguments aren't used, nobody cares that they are undefined.


Function Overloading - Examining what Types of Arguments Were Passed

In javascript, you can also examine what type of arguments were passed to a function and adapt your behavior accordingly. For example, lets say you had a simple function that hids a DOM element:

function hide(elem) {
    elem.style.display = "none";
}

// usage
var obj = document.getElementById("test");
hide(obj);

This function assumes that elem is a DOM element and will only work if that is what is passed. But, what if we also want to allow someone to pass an id of an element. We could examine the argument that is passed to hide and see if it's a string and, if so, we can treat it like an id value.

function hide(elem) {
    // if a string was passed, find the DOM object using that string as an id
    if (typeof elem === "string") {
        elem = document.getElementById(elem);
    }
    elem.style.display = "none";
}

// first usage
var obj = document.getElementById("test1");
hide(obj);

// second uage
hide("test2");

Now, suppose we wanted to allow not only a single DOM element to be passed, but also an array of DOM elements so the function would operate on every DOM element in the array.

function hide(elem) {
    // if a string was passed, find the DOM object using that string as an id
    if (typeof elem === "string") {
        document.getElementById(elem).style.display = "none";
    } else if (typeof elem === "object" && Object.prototype.toString.call(elem) === "[object Array]") {
        // an array was passed
        for (var i = 0; i < elem.length; i++) {
            elem[i].style.display = "none";
        }
    } else {
        // only a single DOM element was passed
        elem.style.display = "none";
    }
}

// usages
hide(obj);
hide([obj1, obj2, obj3]);
hide("test");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top