5

Refering to this topic: Don't make functions within a loop. - jslint error

How would you handle a jquery .each(function () {...} within a for loop? knowing that i need the context of the "for" in my "each" function. Sure I could map every required to parameters to a function a function declared outside of the loop but from my perspective, it impacts the readability.

Any thoughts?

Thanks in advance.

Community
  • 1
  • 1
Harps
  • 640
  • 8
  • 17

3 Answers3

9

Well, you can keep the context of the for in your loop, as everything in the for is actually in the same context as a function declared at the start.

So let's take Frits' example, but first let's make this fully JSLint happy (minus the function called in the loop error) first.

/*global console*/
var my_func, i, list;
for (i = 0; i < list.length; i+= 1) {
    my_func = function (i) {
        console.log(i);
    };
    my_func(i);
}

Note that each time you iterate the loop, you're redeclaring the my_func function. That's not cool! Why re-declare the same function over and over?

Declare it earlier, like this:

/*global console*/
var my_func, i, list;

my_func = function (i) {
    console.log(i);
};

for (i = 0; i < list.length; i+= 1) {
    my_func(i);
}

Success. Now you don't create a function with each iteration. And, as JSLint helps you realize by pushing all your var declarations to the top, you still get to have the same context.


EDIT: As @Flame points out, you don't have to declare the function early with jQuery each and can use an anonymous function, but it's not a bad idea to declare early, especially if you're doing to reuse the logic in multiple each calls. The main take-homes are to understand that 1.) The early declaration practice still has advantages, and 2.) jQuery's still going to send along arguments to your function (here, what we're calling index), though JSLint will not (and should not) complain about anonymous functions used in eachs (jQuery sauce here).

It is initially a little more unintuitive if you're used to the anonymous $.each(function() {}); construct, but it's just as easy.

/*global alert, $ */
$( "li" ).each(function( index ) {
    alert( index + ": " + $(this).text() );
});

... turns into...

/*global $, alert */
var fnOutside = function(index) {
    alert( index + ": " + $(this).text() );
};
$( "li" ).each(fnOutside);

That might be confusing briefly because the function call doesn't have parameters, but jQuery is what's pushing the "index" parameter to the function. You could just grab the contextual arguments array to see that it's still pushed in there if you wanted to leave the naming out.

Fiddle-ige

That is to say, the for construct doesn't create a new closure. That might be worrying you. It's no problem!

Community
  • 1
  • 1
ruffin
  • 16,507
  • 9
  • 88
  • 138
  • you're right, that's the way to do to make jslint happy and keep the code as readable as possible. That's the solution I describe as impacting readability in my initial question. with a single parameter it's ok, but if you consider using 10... – Harps Mar 28 '13 at 07:07
  • 1
    Sure, apologies for going too basic for your question. The real crux for me is the bolded portion, above, specifically that *declaring inside the loop reduces performance* (as you redeclare the same function each time) and breaks logical "normalization", which is reason enough (imo) to put it in a slightly more unreadable state, just above the `for`. You could always use a compound object to keep it trivially more readable (`options = {var1: "val1", var2: "val2"...}`) or bind to something useful. Either way, I take JSLint's advice in these cases. – ruffin Mar 28 '13 at 20:36
  • @rufin That might be confusing briefly because the function call doesn't have parameters, but jQuery is what's pushing the "index" parameter to the function. jQuery also pushes a second param, namely the element, which might be less confusing than using `$(this).` /*global $, alert */ var fnOutside = function(index, el) { alert( index + ": " + $(el).text() ); }; $( "li" ).each(fnOutside); – user2720770 Aug 27 '13 at 08:26
  • that last example where you do `$('li').each(fnOutside)` doesnt make a single difference. You are only calling the function `each` once. As soon as you are within that function, the parameter `fnOutside` you passed wont be changed again. Also, jslint/jshint will only call these kind of errors when you use a for-loop explicitly. It cant know the contents of `jQuery.each` – Flame Jun 18 '15 at 10:27
  • @Flame -- You're right. I'm not sure why I felt that was important two years ago, other than making it explicit that jQuery would still push the argument into the previously declared function. I do remember some folks having a hard time getting that at work, and maybe that clouded my answer at the time. But, again, you're right, JSLint doesn't (& shouldn't!) complain if you use an anonymous function there, as it's only declared once. [jQuery source here](https://github.com/jquery/jquery/blob/bb026fc12c3c2ad37f47f0919e484bddcdc3d291/src/core.js#L270): `each: function( obj, callback ) {...` – ruffin Jun 18 '15 at 12:33
  • well thx for assuring :) i was having doubts on similar issues in my own code and I came across this answer – Flame Jun 18 '15 at 13:20
4

The problem with functions in loops is: this,

for (var i = 0; i < list.length; i+= 1) {
    function my_func(i) {
        console.log(i);
    }
    my_func(i);
}

Will be interpretted as:

var my_func;
for (var i = 0; i < list.length; i+= 1) {
    my_func = function (i) {
        console.log(i);
    }
    my_func(i);
}

Because of variable hoisting. All declarations will be moved to the top of the scope (ie. function). So it doesn't actually make sense to define a function in a for-loop.

If you want to bind to the counter in a for-loop simply make a closure.

Just to be clear:

$("..").each(function () {
    for(..) {
    }
});

is fine.

for(..) {
    $("..").each(function () {

    });
}

is not fine because it's equivalent to this:

var my_func;
for(..) {
    my_func = function () {
        // ..
    }
    $("..").each(my_func);
}
Halcyon
  • 57,230
  • 10
  • 89
  • 128
2

It already sounds like the problem is more with the warning than with your code. Yeah it'll be nice to pass the JSLint test without warnings but some of them can be unreasonable and cause unnecessary code refactorization.

The discussion in this Q&A is relevant: Should I use JSLint or JSHint JavaScript validation? Other lint tools are available with more flexibility than JSLint so you can continue to be productive with your personal coding preferences, not necessarily Crockford's personal coding preferences.

I suggest you consider using another tool to lint your JavaScript and don't sacrifice readability or maintainability of your code.

Community
  • 1
  • 1
Rick Viscomi
  • 8,180
  • 4
  • 35
  • 50
  • Sure, but still.. It forces to ask good questions. ruffin's comment about the functionbeing redeclared over and over is clearly the reason of this rule which finally make sense. – Harps Mar 28 '13 at 08:05