Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSlint error 'Don't make functions within a loop.' leads to question about Javascript itself

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?

like image 894
Zhami Avatar asked Oct 13 '10 18:10

Zhami


2 Answers

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.

like image 94
T.J. Crowder Avatar answered Nov 15 '22 09:11

T.J. Crowder


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.

like image 24
casablanca Avatar answered Nov 15 '22 10:11

casablanca