Steven Lu
Steven Lu

Reputation: 43457

Javascript Function Scoped For Loops

Here's an example of a situation where a simple JS loop does not behave as expected, because of the loop variable not being in a separate scope.

The solution often presented is to construct an unpleasant-looking bit of loop code that looks like this:

for (var i in obj) {
    (function() {
        ... obj[i] ... 
        // this new shadowed i here is now no longer getting changed by for loop
    })(i);
}

My question is, could this be improved upon? Could I use this:

Object.prototype.each = function (f) {
    for (var i in this) {
        f(i,this[i]);
    }
};

// leading to this somewhat more straightforward invocation
obj.each(
    function(i,v) {
        ... v ...
        // alternatively, v is identical to
        ... obj[i] ...
    }
);

when I ascertain that I need a "scoped loop"? It is somewhat cleaner looking and should have similar performance to the regular for-loop (since it uses it the same way).

Update: It seems that doing things with Object.prototype is a huge no-no because it breaks pretty much everything.

Here is a less intrusive implementation:

function each (obj,f) {
    for (var i in obj) {
        f(i,obj[i]);
    }
}

The invocation changes very slightly to

each(obj,
    function(i,v) {
        ... v ...
    }
);

So I guess I've answered my own question, if jQuery does it this way, can't really go wrong. Any issues I've overlooked though would warrant an answer.

Upvotes: 5

Views: 158

Answers (2)

tiffon
tiffon

Reputation: 5040

Your answer pretty much covers it, but I think a change in your original loop is worth noting as it makes it reasonable to use a normal for loop when the each() function isn't handy, for whatever reason.

Update: Changed to use an example that's similar to the example referenced by the question to compare the different approaches. The example had to be adjusted because the each() function requires a populated array to iterate over.

Assuming the following setup:

var vals = ['a', 'b', 'c', 'd'],
    max = vals.length,
    closures = [],
    i;

Using the example from the question, the original loop ends up creating 2n functions (where n is the number of iterations) because two functions are created during each iteration:

for (i = 0; i < max; i++) {
    closures[i] = (function(idx, val) {  // 1st - factoryFn - captures the values as arguments 
        return function() {              // 2nd - alertFn   - uses the arguments instead
            alert(idx + ' -> ' + val);   //                   of the variables
        };
    })(i, vals[i]);
}

This can be reduced to creating only n + 1 functions by creating the factory function once, before the loop is started, and then reusing it:

var factoryFn = function(idx, val) {
    return function() {
        alert(idx + ' -> ' + val);
    };
};

for (i = 0; i < max; i++) {
    closures[i] = factoryFn(i, vals[i]); 
}

This is nearly equivalent to how the each() function might be used in this situation, which would also result in a total of n + 1 functions created. The factory function is created once and passed immediately as an argument to each().

each(vals, function(idx, val) {
    closures[idx] = function() {
        alert(idx + ' -> ' + val);
    };
});

FWIW, I think a benefit to using each() is the code is a bit shorter and creating the factory function right as it's passed into the each() function clearly illustrates this is its only use. A benefit of the for loop version, IMO, is the code that does the loop is right there so it's nature and behavior is completely transparent while the each() function might be defined in a different file, written by someone else, etc.

Upvotes: 1

Ashish Kumar
Ashish Kumar

Reputation: 1005

Global Scope

When something is global means that it is accessible from anywhere in your code. Take this for example:

var monkey = "Gorilla";

function greetVisitor () {

return alert("Hello dear blog reader!");
}

If that code was being run in a web browser, the function scope would be window, thus making it

available to everything running in that web browser window.

Local Scope

As opposed to the global scope, the local scope is when something is just defined and accessible in a

certain part of the code, like a function. For instance;

function talkDirty () {

var saying = "Oh, you little VB lover, you";
return alert(saying);
}

alert(saying); // Throws an error

If you take a look at the code above, the variable saying is only available within the talkDirty

function. Outside of it it isn’t defined at all. Note of caution: if you were to declare saying without

the var keyword preceding it, it would automatically become a global variable.

What this also means is that if you have nested functions, the inner function will have access to the

containing functions variables and functions:

function saveName (firstName) {

function capitalizeName () {

return firstName.toUpperCase();

} var capitalized = capitalizeName();

return capitalized;

}

alert(saveName("Robert")); // Returns "ROBERT"

As you just saw, the inner function capitalizeName didn’t need any parameter sent in, but had complete

access to the parameter firstName in the outer saveName function. For clarity, let’s take another

example:

function siblings () {

var siblings = ["John", "Liza", "Peter"];

function siblingCount () {

var siblingsLength = siblings.length;

return siblingsLength;

}

function joinSiblingNames () {

return "I have " + siblingCount() + " siblings:\n\n" + siblings.join("\n");

}

return joinSiblingNames();

}

alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"

As you just saw, both inner functions have access to the siblings array in the containing function, and

each inner function have access to the other inner functions on the same level (in this case,

joinSiblingNames can access siblingCount). However, the variable siblingsLength in the siblingCount is

only available within that function, i.e. that scope.

Upvotes: 1

Related Questions