6

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.

Community
  • 1
  • 1
Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • 1
    Check out JS libraries as to how they implement the `Object.each()` shim. As for performance and optimization, test your code using [JSPerf](http://jsperf.com) and you could ask over at [CodeReview](http://codereview.stackexchange.com/) – Joseph Jan 01 '13 at 06:08
  • Yes I took a look at `jQuery.each()` just now and it looks like my implementation simply flips their argument order around and does fewer (none, actually) checks. – Steven Lu Jan 01 '13 at 06:13

2 Answers2

1

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.

Community
  • 1
  • 1
tiffon
  • 5,040
  • 25
  • 34
  • I think this makes the loop unnecessarily asynchronous. It is liable to cause nasties like race conditions when that can clearly be avoided. – Steven Lu Jan 05 '13 at 00:24
  • Due to the use of `setTimeout()`? I'm using that as an example; I didn't see the example you linked to... No idea how I managed to miss it. I've revised the answer to use something more like what you've referred to in the question. Or, is your comment in reference to something besides `setTimeout()`? – tiffon Jan 05 '13 at 02:07
  • Yeah. TBH I don't think I really looked at why you were using it, I do actually use `setTimeout()` to do a lot of cool things but it is also true that it is a good idea to avoid it if it isn't necessary and that was why I wrote the comment but I probably should have read your answer more thoroughly. – Steven Lu Jan 05 '13 at 02:43
1

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.

Ashish Kumar
  • 975
  • 7
  • 6