2

Douglas Crockford, and thus, JSLint really dislike for loops. Most of the time I agree, and use [].forEach and Object.keys(obj) to iterate through arrays or dictionaries.

However, there are cases where the built in functions are not better than a for loop in any reasonable sense (as far as I know), and I dislike having warnings on my JSLints.

One such case is, when one has a bunch of items on a canvas. When drawing, you want to iterate through the array forwards, but when hit testing a mouse click, you need to iterate backwards. E.g.

// ...
drawItems = function () {
    objects.forEach(drawItem);
},
hitTest = function (x, y) {
    var i;
    for (i = objects.length - 1; i >= 0; i -= 1) {
        if (test(objects[i], x, y)) {
            return objects[i];
        }
    }
}
// ...

I can think of a couple of ways to pass the test, but they are all clunky. E.g.

hitTest = function (x, y) {
    var item;

    objects.reverse();
    objects.some(function (v) {
        if (test(v)) {
            item = v;
            return true;
        }
    });
    objects.reverse();
    return item;
}

and

hitTest = function (x, y) {
    return objects.reduceRight(
        function (prev, curr) {
            return prev || (
                test(curr, x, y)
                    ? curr
                    : prev
            );
        },
        undefined
    );
}

Which will pass JSLint happily, but, to be honest, look disgusting. I don't like the idea of "hacking" my code just to make it pass a test.

Other than choosing the "tolerate for statement" option (because that affects the linting of the rest of the code), how would I deal with this whilst avoiding wasteful operations like reversing or copying the array?

Vincent McNabb
  • 33,327
  • 7
  • 31
  • 53
  • 2
    Why are you letting Crockford dictate your programming style? If you're sure you're doing it the right way, ignore jslint. – Barmar Jun 04 '15 at 23:21
  • 2
    Have you considered that you don't need to pass a JSLint test in order to have good working code? – scunliffe Jun 04 '15 at 23:22
  • The only good answers here are to ignore the jsLint warning, switch to a more flexible tool like jsHint that will let you embed a comment to skip this warning. You should not make your code obscure and less efficient just to remove a warning that shouldn't be there in the first place. jsLint is far too dictatorial in ways that are not always the best code. – jfriend00 Jun 04 '15 at 23:31
  • @Barmar I am certainly taking the path of ignoring this particular option. I was wondering whether there was a better way, as I certainly don't know all there is to know about Javascript. – Vincent McNabb Jun 04 '15 at 23:40
  • 2
    @Barmar & @scunliffe (& @jfriend00) (Warning: *Pet peeve*:) I'm not sure "use JSHint" comments really add to the JSLint tag much, as a general rule. Crockford's not an idiot. If someone doesn't want to use his tools, fine, but there's usually something important to learn from his "suggestions". The fact that there is a `for:true` JSLint directive shows he knows his `for` proscription is pretty controversial, and he doesn't really mind you using it for now. Now ask yourself why he prefers you don't, as the OP has... /shrug – ruffin Jun 05 '15 at 02:03
  • There's an earlier JSLint answer on `for` that found a quote from YouTube of why Crockford doesn't like `for` loops [here](http://stackoverflow.com/a/30518734/1028230), fwiw. (Great question here, though, btw.) – ruffin Jun 05 '15 at 02:06
  • @ruffin I guess my real question (which doesn't fit the format of this site) it "what would Douglas Crockford do in this case?" Of course that wouldn't fit the Q&A format of this site, and even this question is pushing that boundary. It's situations like these where the solution is worse than the "problem" that turn me off wanting to listen to that man at all. – Vincent McNabb Jun 05 '15 at 03:29
  • @ruffin Crockford seems to have a number of ideosyncratic coding style opinions, and JSLint warns when you violate them. Warning against `for` loops is the weirdest one I've heard about -- just because ES5 has higher-level functions, he thinks everyone should stop using this simple, well-understood feature. – Barmar Jun 05 '15 at 04:22
  • @Barmar I've still never seen him be demonstrably *wrong* about anything, though, and the most controversial stuff (like this `for` proscription) seem to always have directives so you can avoid them in a stock JSLint install if you want. If I had to inherit legacy code sight unseen, and could only require that it be JSLinted *or* JSHinted (pick one), I'd pick JSLinted code every time. – ruffin Jun 05 '15 at 15:30

3 Answers3

2

Ran into an interesting, reverse-free alternative fix here -- see the 3rd & 4th -- while looking at something else today. Easy enough to swap from jQuery's each to forEach or every. You have to get the length for your original for anyhow, so actually pretty clean, though a little hipster.

/*jslint white:true, devel:true */
var a = [3,4,5,6], max = a.length-1;
a.forEach(function (ignore, i) {
    "use strict";
    console.log(a[max-i]);
});

I'm a little worried about performance, since you're pulling out elements twice with each iteration, once at a[max-i], and once with the forEach's callback's (here, throwaway) third argument, though maybe that's a micro-optimization concern.

Using forEach only for the indicies is what's setting off the hipster warning. Ironic, since Crockford has shown a stark dislike for hipsters in the past, so I'm not sure he'd like this "fix" either -- none of us may have answered your "meta-question". Tempted to bug Crockford's list; this is a good and interesting question, but you don't always get a good answer on that list.

But that's a pretty (deceptively?) clean looking workaround.

Community
  • 1
  • 1
ruffin
  • 16,507
  • 9
  • 88
  • 138
  • Nice one :-) Although it looks better than the other non-for-loop alternatives, once understanding what it does, it still feels very very wrong. I'd proffer that people who blindly follow Crockford (or anyone else) are "hipsters" because they're doing something that's "right" because someone said so, rather than finding the optimal solution by thinking for themselves about the pitfalls. I also don't like having to put "ignore" in function calls to pass JSLint. The parameter IS a value, so it should be called that even if it's ignored. – Vincent McNabb Jun 08 '15 at 21:52
  • Yeah, I'm partial to using the `for` directive in JSLint myself right now. I like the LINQ-like format of `every` & `forEach`, but ultimately "*[The for syntax is so weird anyway with the thing with the three statements in it,](http://stackoverflow.com/questions/30518554/jslint-unexpected-for/30518734#30518734)*" doesn't feel like a solid, rational justification to completely leave a well-understood programming convention. I did [post about your question to Crockford's JSLint Google+ group](https://plus.google.com/112458378606572101777/posts/LbxKj8SCNHM). Hopefully he'll have a good answer. – ruffin Jun 08 '15 at 22:36
1

Since I'm of the opinion that you shouldn't de-optimize your code just to make a pointless warning go away, here are some of your options for dealing with this:

  1. Ignore the misguided warning.
  2. Switch to jsHint which allows you to instruct jsHint to ignore certain sections of code (with embedded comments).
  3. Isolate the code that jsLint is unhappy with in a function or a method so the warning only occurs once in your entire source rather than every time you want to do a reverse traverse.

For example, here's a reverse traverse function:

// traverse an array in reverse order
// if the callback returns true, iteration will be stopped
function reverseForEach(array, fn) {
    for (var i = array.length - 1; i >= 0; i--) {
        if (fn(array[i], i, array) === true) {
            return;
        }
    }
}

Or, as a method on Array prototype:

// traverse an array in reverse order
// if the callback returns true, iteration will be stopped
Array.prototype.reverseForEach = function(fn) {
    for (var i = this.length - 1; i >= 0; i--) {
        if (fn(array[i], i, this) === true) {
            return;
        }
    }
}
Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    I ended up adding a prototype function called 'findRight' and 'find', and have them in a file which I don't bother JSLinting, along with a bunch of other Crockford unfriendly code. – Vincent McNabb Jun 05 '15 at 01:59
0

You can slice the array so that your hitTest doesn't have any potential side-effects.

hitTest = function (x, y) {
    return objects.slice(0).reverse().find(function(v) {
        return test(v);
    });
}

This way you don't have to worry that you'll forget to re-reverse the array.

aebabis
  • 3,657
  • 3
  • 22
  • 40
  • 1
    Copying and reversing the array, just to traverse the array backwards is downright wasteful. In addition, the `.find()` method is not supported in IE or Chrome so not much good for general use. – jfriend00 Jun 04 '15 at 23:26
  • 1
    @jfriend00 `some()`, which was used in the question, also only has partial support, so I assume the OP doesn't mind. `slice` is really fast, especially in Chrome, and while `reverse` may be slower, it shouldn't matter if it makes more readable code. Since this code is run in response to user input, readability matters more than saving a few ms, IMO. – aebabis Jun 04 '15 at 23:31
  • 1
    `.some()` has much broader browser support than `.find()`. And, why copy the array just to traverse it? The whole notion of adding complication to your code just to bypass a jsLint warning that shouldn't be there in the first place is just dumb. jsLint is what is wrong here, not the code. – jfriend00 Jun 04 '15 at 23:32
  • @jfriend00 I don't personally agree with JSLint in this particular case either, but also I think not wanting a loop is a legitimate preference and a matter of opinion. The code I posted is more declarative, and some people would probably find it easier to read. – aebabis Jun 04 '15 at 23:41
  • Besides, it's not like `.find()` or `.forEach()` don't have a loop in them - they do - it's just hidden behind a function call. – jfriend00 Jun 05 '15 at 00:41
  • @jfriend00 The point isn't whether or not there's a loop; its whether or not it *looks like* a loop. Some people prefer their code to look like natural language. – aebabis Jun 05 '15 at 05:14