Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Function Scoped For Loops

Tags:

javascript

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.

like image 798
Steven Lu Avatar asked Jan 01 '13 06:01

Steven Lu


People also ask

DO FOR loops have scope?

The scoping semantics of the for loop dictate that after each execution of the loop, the object that was created during that iteration will be destroyed. Technically, the f is in a more inner scope than the i .

What is function scoped in JavaScript?

JavaScript has function scope: Each function creates a new scope. Variables defined inside a function are not accessible (visible) from outside the function. Variables declared with var , let and const are quite similar when declared inside a function.

How do you take the value out of a for loop?

have an hidden element say an input. set the value of it inside the loop with your value desired. call the change event along for the input element. Add a event listener for the change of input and get that value which is obviously outside the loop.

Is Let block scoped or function scoped?

The let declaration declares a block-scoped local variable, optionally initializing it to a value.


2 Answers

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.

like image 51
tiffon Avatar answered Oct 09 '22 17:10

tiffon


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.

like image 40
Ashish Kumar Avatar answered Oct 09 '22 16:10

Ashish Kumar