28

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?

Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
Zhami
  • 19,033
  • 14
  • 48
  • 47
  • 3
    The warning is probably for those who might think that `i` would evaluate to 0 inside the function the first time through the loop, and 1 the second time, etc. It's an easy mistake to make. – Dagg Nabbit Oct 14 '10 at 06:48
  • I didn't mean for my question to be a discussion of JSLint. Thanks all who provided deep insight into JS -- you guys rock. – Zhami Oct 14 '10 at 12:56
  • 2
    I came across this a while ago when I was looking for explanations as to why this isn't a good idea. I came across this again as I was cleaning up code, and I decided to [make a lil' test](http://jsperf.com/defined-function-vs-in-loop-function) and the results are staggering: the anonymous function in a loop runs almost **780,000** ops/sec slower than defining the function outside of the loop - yikes! – phatskat Mar 19 '14 at 21:31

4 Answers4

42

Partially it depends on whether you're using a function expression or a function declaration. They're different things, they happen at different times, and they have a different effect on the surrounding scope. So let's start with the distinction.

A function expression is a function production where you're using the result as a right-hand value — e.g., you're assigning the result to a variable or property, or passing it into a function as a parameter, etc. These are all function expressions:

setTimeout(function() { ... }, 1000);

var f = function() {  ... };

var named = function bar() { ... };

(Don't use that last one — which is called a named function expression — implementations have bugs, particularly IE.)

In contrast, this is a function declaration:

function bar() { ... }

It's stand-alone, you're not using the result as a right-hand value.

The two main differences between them:

  1. Function expressions are evaluated where they're encountered in the program flow. Declarations are evaluated when control enters the containing scope (e.g., the containing function, or the global scope).

  2. The name of the function (if it has one) is defined in the containing scope for a function declaration. It is not for a function expression (barring browser bugs).

Your anonymous functions are function expressions, and so barring the interpreter doing optimization (which it's free to do), they'll get recreated on each loop. So your use is fine if you think implementations will optimize, but breaking it out into a named function has other benefits and — importantly — doesn't cost you anything. Also, see casablanca's answer for a note about why the interpreter may not be able to optimize out recreating the function on each iteration, depending on how deeply it inspects your code.

The bigger issue would be if you used a function declaration in a loop, the body of a conditional, etc.:

function foo() {
    for (i = 0; i < limit; ++i) {
        function bar() { ... } // <== Don't do this
        bar();
    }
}

Technically, a close read of the spec's grammar shows it's invalid to do that, although virtually no implementation actually enforces that. What the implemenations do is varied and it's best to stay away from it.

For my money, your best bet is to use a single function declaration, like this:

function foo() {
    for (i = 0; i < limit; ++i) {
        bar();
    }

    function bar() {
        /* ...do something, possibly using 'i'... */
    }
}

You get the same result, there's no possibility that an implementation will create a new function on every loop, you get the benefit of the function having a name, and you don't lose anything.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 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 Oct 13 '10 at 19:15
  • 2
    @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 Oct 13 '10 at 19:17
  • +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 Oct 13 '10 at 19:21
  • 1
    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 Oct 13 '10 at 19:28
  • 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 Oct 13 '10 at 21:29
  • I definitely agree that anything that looks like a function declaration is best avoided except where a function declaration is valid. – Tim Down Oct 13 '10 at 21:30
  • @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 Oct 13 '10 at 21:50
  • @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 Oct 13 '10 at 21:57
  • @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 Oct 13 '10 at 21:59
  • 1
    @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 Oct 13 '10 at 22:23
  • @T.J.Crowder: Ahh, I see the problem now. :) – casablanca Oct 13 '10 at 23:18
  • In the last two examples, aren't you forgetting that function declarations get hoisted to the top of the scope? In the 2nd to last example, the function will get yanked out of the loop to the top. And in the last example, `function bar() { ... }` is interpreted before the loop. At least that's what I learned from this article: http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html And why are anonymous functions considered expressions and not declarations? They look more like declarations for me! – M.K. Safi Aug 31 '13 at 10:56
  • 2
    @MKSafi: No, I'm not forgetting that. The first one, where there's a declaration in the loop, is simply invalid JavaScript (currently). A declaration is simply not allowed there. Yes, in the last example, the `bar` function is created prior to the loop -- that's the point of my having it there and saying (in effect) "here's what I would do." Re expression vs. declaration: If it's used as a *right-hand value* (so, the right-hand side of an assignment or initialization, or passed into a function, or it has an operator in front of it), it's an expression. If not, it's a declaration. – T.J. Crowder Aug 31 '13 at 12:06
  • @T.J.Crowder, I see. So, when the function is used for its value, it's an expression. Otherwise, it's just being declared. – M.K. Safi Aug 31 '13 at 12:36
  • 1
    @MKSafi: Right. And of course, declarations *must* have a name, whereas in expressions, names are optional. Unfortunately, named function expressions [don't work correctly](http://blog.niftysnippets.org/2010/09/double-take.html) in IE8 and earlier. – T.J. Crowder Aug 31 '13 at 12:58
24

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
  • 69,683
  • 7
  • 133
  • 150
  • 3
    Note that just because separate `Function` *objects* are created, it doesn't mean they don't share the same *code*, at least on some JavaScript engines: http://groups.google.com/group/v8-users/browse_thread/thread/05bbcbe6a146fee7 – T.J. Crowder Jan 16 '12 at 08:11
2

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
  • 258,201
  • 41
  • 486
  • 479
2

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!

  • 2
    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 Oct 13 '10 at 22:05
  • 1
    @Crowder There are many ways to do something; this is one of the idioms I use :-) Btw, really like your answer. –  Oct 13 '10 at 22:10