views:

111

answers:

4

I have some code that invokes anonymous functions within a loop, something like this pseudo example:

for (i = 0; i < numCards; i = i + 1) {
    card = $('<div>').bind('isPopulated', function (ev) {
        var card = $(ev.currentTarget);
        ....

JSLint reports the error 'Don't make functions within a loop.' I like to keep my code JSLint clean. I know I can move the anonymous function out of the loop and invoke it as a named function. That aside, here's my question:

Would a Javascript interpreter really create an instance of the function per iteration? Or is there really only one function instance "compiled" and the same code is executed repeatedly? That is, does the JSLint "suggestion" to move the function out of the loop actually affect the efficiency of the code?

+2  A: 

The interpreter may actually create a new function object with every iteration, if only because that function might be a closure that needs to capture the current value of any variable in its outer scope.

That's why JSLint wants to scare you away from creating many anonymous functions in a tight loop.

Frédéric Hamidi
+9  A: 
T.J. Crowder
+1 for pointing out the expr/statement difference.
pst
A declaration within a loop isn't really invalid. All declarations are instantiated when the outer function is invoked, so it makes no difference whether it's inside a loop or outside.
casablanca
@casablanca: It's invalid if you read the grammar in the spec. It's particularly important to consider the conditional case: `if (a) { function foo() { ... } } else { function foo() { ... } }`. Which is **why** it's invalid if you read the grammar. ;-)
T.J. Crowder
+1, you've got some detailed JS knowledge there that most don't. i think technically, though, your last example is also invalid. function declarations are only strictly valid at the top-level. but its all good.
Claudiu
Sorry, I just checked the spec and you're right. But it seems to me that if the grammar would allow it, it would behave the same way as `if (a) { var b; } else { var c; }` which is the same as if everything had been declared at the beginning of the function.
casablanca
A clarification: the way Mozilla (other browsers seem to do a similar thing) appears to allow function declarations within (non-function) blocks is by introducing an extension to ECMAScript called a *function statement* (not to be confused with the function declaration, and the reason why it's unfortunate that a lot of people say "function statement" when they mean "function declaration"). A function statement is closer to a function expression than a function declaration. kangax's excellent article has the details: http://kangax.github.com/nfe/#function-statements
Tim Down
I definitely agree that anything that looks like a function declaration is best avoided except where a function declaration is valid.
Tim Down
@Claudiu: The final example is valid. Function declarations can be nested inside functions, just not inside control flow statements. This is explicitly allowed. The declaration is evaluated upon entry into the containing scope (function, in this case).
T.J. Crowder
@casablanca: *"But it seems to me that if the grammar would allow it, it would behave...the same as if everything had been declared at the beginning..."* The problem is if you declare two `foo` functions with different bodies, which one is takes precedence? Completely take your point about the analogy with `var`, but it doesn't quite work because `var` can't be used to declare two completely different things. (A `var` with an initializer becomes a `var` [happens on entry to scope] and an assignment [happens at code flow]; the same thing doesn't happen with function declarations.) :-)
T.J. Crowder
@Tim Down: Thanks for the kangax link, I didn't have it to hand (but it's where I learned of the NFE oddities in browsers).
T.J. Crowder
@T.J. Crowder: ah you're right, i just checked the spec. `FunctionBody` contains `SourceElements` which contains `SourceElement` s which contain both `Statement` s and `FunctionDeclaration` s
Claudiu
@T.J.Crowder: Ahh, I see the problem now. :)
casablanca
+1  A: 

Boo to JSLint. It's like a blunt instrument on the head. A new function object is created each time function is encountered (it is a statement/expression, not declaration -- edit: this is a white lie. See T.J. Crowders answers). Usually this is done in a loop for a closure, etc. The bigger issue is creating false closures.

For instance:

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    alert(i)
  }, 10)
}

Will result in "odd" behavior. This isn't an issue with "creating a function in a loop so much as not understanding the rules JS uses for variable scopes and closures (variables are not bound in closures, scopes -- execution contexts -- are).

However, you may want to create a closure in a function. Consider this less-surprising code:

for (var i = 0; i < 10; i++) {
  setTimeout((function (_i) { 
    return function () {
      alert(_i)
    }
  })(i), 10)
}

Oh no! I still created a function!

pst
Re your less-surprising code: There's still no reason to (re)create your factory function on every iteration of the loop, which (in theory) that code does. You only want to *call* your factory function on each iteration (which will in turn create a function and return it, which is perfectly reasonable). Here's the reformulation that avoids the redundancy (and the JSLint warning): http://pastie.org/pastes/1219371
T.J. Crowder
@Crowder There are many ways to do something; this is one of the idioms I use :-) Btw, really like your answer.
pst
+4  A: 

Would a Javascript interpreter really create an instance of the function per iteration?

It has to because it doesn't know if the function object will be modified elsewhere. Remember that functions are standard JavaScript objects, so they can have properties like any other object. When you do this:

card = $('<div>').bind('isPopulated', function (ev) { ... })

for all you know, bind could modify the object, for example:

function bind(str, fn) {
  fn.foo = str;
}

Clearly this would result in wrong behaviour if the function object was shared across all iterations.

casablanca
Very, very good point about the function getting modified!
T.J. Crowder
+1 -- great point!
Zhami