52

Consider the following code:

for (var i=0; i<100; i++) {
    // your code here
}
// some other code here
for (var i=0; i<500; i++) {
    // custom code here
}

Any decent lint tool (jslint, jshint or built in IDE) will tell warning - duplicate declaration of variable i. This can be solved by using variable with another name (k, j) or moving declaration to the top:

var i; // iterator
for (i=0; i<100; i++) {}
for (i=0; i<500; i++) {}

I am not fond of both variants - I don't make declarations at the top usually (and even if I did I wouldn't want see there helper variables - i, j, k) and really nothing bad is going on in those examples to change variables' names for.

Though I do want a huge warning in case I write something like this:

for (var i=0; i<100; i++) {
    for (var i=0; i<500; i++) {} // now that's bad
}

What's your approach to such cases?

Ilya Tsuryev
  • 2,766
  • 6
  • 25
  • 29

4 Answers4

50

JavaScript has many constructions which look like well-known constructions in other computer languages. It's dangerous for JavaScript to interpret the construction in another way as most other computer languages.

If somebody who doesn't know JavaScript good enough (the common case by the way) sees the construction like

for (var i=0; i<100; i++) {
    // your code here
}

or sees declaration of the variable in the block

{
    var i;
    //some code
    {
        var j;
        // some code
    }
}

then most readers will think that block level variables will be defined. JavaScript don't have block level variables. All variables will be interpreted as function level defined.

So I never define variables inside of the code if the code is not just some test code which will be written for 5 min only. The main reason is that I don't want to write code and use language constructions which could be misunderstood.

By the way JSLint finds defining of variables inside of block such bad style that it stop processing of the code analysis. There are no JSLint options which could change this behavior. I find the behavior of JSLint not good, but I agree that declaration of variables inside of for loop is bad because it will be read by most persons as code with local loop variables, which is incorrect.

If you use

for (var i=0; i<100; i++) {
    // your code here
}
// some other code here
for (var i=0; i<500; i++) {
    // custom code here
}

then JavaScript moves all declarations of variables at the beginning of the function for you. So the code will be as

var i = undefined, i = undefined; // duplicate declaration which will be reduced
                                  // to one var i = undefined;

for (i=0; i<100; i++) {
    // your code here
}
// some other code here
for (i=0; i<500; i++) {
    // custom code here
}

So please think about other readers of the code. Don't use any constructions which could be interpreted in the wrong way.

user66001
  • 774
  • 1
  • 13
  • 36
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • 14
    so what's the correct way of dealing with this double for loops 'i' declarations? – Andrzej Rehmann Jul 29 '14 at 07:20
  • 5
    @Hoto: **Don't use `var i` inside of `for`**. Instead of that declare `var i` once at the beginning of `function` where you use `for` loop. – Oleg Jul 29 '14 at 08:11
  • So you're writing code based on the assumption that the other guy doesn't understand the language. You're effectively pretending that JS has block scope, so that the ignorant programmer will not be confused about whether it does. Maintaining a list of all local variables on top of the function is a mess, and JavaScript doesn't require it for good reason. The top of the function cannot be expected to be omniscient about all subsequent code. – Val Kornea Aug 20 '14 at 05:01
  • 1
    @user2407309: I do declare variable multiple times in *long* JavaScript code, but I *never* use `var` inside of loop. I try to write the code which will be correct (without misunderstanding) interpreted by *people*. So my approach is *accepting* the rules of the current version of JavaScript language which I use and trying writing the code in the style which will be read in the same way by most readers (people) and by every web browser. – Oleg Aug 20 '14 at 05:11
  • 12
    How could `for (var i = 0; i < someVar.length; i++) {}` possibly be misunderstood by people? There is nothing ambiguous about it. The only way you could "misunderstand" it is if you think that JavaScript has block scope. Your suggestion is not an instance of the virtue of writing understandable code, nor of accepting the rules of the current version of JavaScript. It is specifically done in order to give ignorant programmers the impression that JavaScript has block scope; there is no other reason to write it like that. Its *only* purpose is to mislead people about how JavaScript works. – Val Kornea Aug 20 '14 at 07:40
  • 1
    @user2407309: The misunderstanding is that one expect that **block-level** (loop-level) variable will be declared and initialized without any side effect on the outer code. If the outer block/function have already another `i` variable then the value will be **overwritten**. After the end of loop `i` of the function will be changed to `someVar.length`. So one can't just include `for (var i=0; ...) {}` block in JavaScript program - one have to validate whether the outer function have the same variable already declared. Another example of bad usage you find at the end of the question: two loops. – Oleg Aug 20 '14 at 07:57
  • 5
    That's not a misunderstanding of the *code*, but of the *language*. If a programmer is under the impression that JS works in a different way than it actually does, the proper solution is to teach them how the language actually works. One does not adopt a programming style for the sole purpose of allowing people to continue being mistaken about how the language works. – Val Kornea Aug 20 '14 at 08:02
  • 1
    @user2407309: Another important example is usage of closures - functions where one uses variables declared in outer scope. For example, `function outer() {var i = 10; function inner () { var j = i; /* we use here successfully outer i*/ ...}}` Then you decide to include somewhere in the body of `inner` function `for (var i=0; ...) {}`. After the modification `j` at the beginning of `inner` function will be initialized to `undefined` instead of the value of `i` from the outer scope. One will don't have such problems if one declare variables on the top of every function. – Oleg Aug 20 '14 at 08:06
  • 3
    If you have nested loops and each uses `i`, then your method would cause *more* problems, not less, because a validator wouldn't catch the error. However, if you use `for (var i = 0; ...)` every time, a typical validator would complain "duplicate declaration" in the nested loop, and you would notice the problem. It would also complain if the loops weren't nested but merely in the same function--but this warning is only to notify you of potential problems, it is not invalid code. You're better off being notified of potential problems, which is something you lose by adopting your method. – Val Kornea Aug 20 '14 at 08:17
  • @user2407309: I think that we speak about absolutely different things. I think always about the following scenario: you have one large JavaScript program and you modify it in some place. If you just place the construction `for (var i=0; ...) {}` in the middle of existing code then the code can looks like OK for people, but it can have side effects which are difficult to locate and to debug. The construct `for (i=0; ...) {}` will be read by people and Javascript in the same way and for understanding one have to look at `i` declared above. – Oleg Aug 20 '14 at 08:38
  • 2
    One thing that this discussion makes clear is the virtue of block scope, which would make this a non-issue. To truly resolve this issue, the proper practice to adopt is to enclose one's loops in closures. Later versions of JavaScript added `Array.prototype.forEach()` for this purpose, but this is not supported by IE7/IE8; jQuery added `jQuery.each()` which works in all browsers, but obviously requires jQuery. If you need support for old browsers and don't want to add code to enable the closure-loops, you can manually enclose your loops within self-executing functions. See my answer below. – Val Kornea Aug 20 '14 at 08:56
  • @user2407309: It's clear that `jQuery.each` defines **new callback function** and so it defines **new scope** of vars. The problem is only that the asked question was about JavaScript and not about jQuery, TypeScript or other. I agree that one can't recommend (at least today) to use constructions from JavaScript 1.6 or higher because IE8 is still not dad. Moreover `for` construct is much quickly as `jQuery.each` (see [here](http://jsperf.com/browser-diet-jquery-each-vs-for-loop) for example). In any way we have to stay on JavaScript language in the answers on the question and can't use jQuery. – Oleg Aug 20 '14 at 09:31
22

let keyword is introduced in javascript 1.7. Please find MDN documentation here

Replacing var to let inside for loop solves the problem and now one can declare local variable inside the scope of a loop. Check out stackoverlow community explanation here: What's the difference between using "let" and "var" to declare a variable?

Code from future:

for (let i = 0; i < someVar.length; i++) {
    // do your thing here
}
Community
  • 1
  • 1
Said Kholov
  • 2,436
  • 1
  • 15
  • 22
8

I recommend enclosing your loops within self-executing functions whose names tell you what the loop is doing. This effectively gives block scope to the loop. For example:

var users = response['users']
;(function appendUsers(){
    for (var i = 0; i < users.length; i++) {
        var user      = users[i]
        var userEmail = user['email']
        var userId    = user['id']
        /* etc */ 
    }
})() // appendUsers

If you do this:

var i
for (i = 0; i < someVar.length; i++) {
    for (i = 0; i < someOtherVar.length; i++) {
        // This is a bug that a validator will not notice.
    }
}

On the other hand:

for (var i = 0; i < someVar.length; i++) {
    for (var i = 0; i < someOtherVar.length; i++) {
        // This is a bug that a validator will warn you about.
    }
}
for (var i = 0; i < yetAnotherVar.length; i++) {
    // It will also warn you about this, but so what?
}

You can stop using i as an iterator:

for (var companyIndex = 0; companyIndex < companies.length; companyIndex++) {
    var company = companies[companyIndex]
}

If you're using jQuery anyway, you can use its jQuery.each() method:

$.each(someVar, function(i, value){
    // etc
})

You can't use Array.prototype.forEach() if you want IE8 and earlier to work, unless you add the Polyfill code or similar.

Val Kornea
  • 4,469
  • 3
  • 40
  • 41
3

I tend the use the array built-in functions, such as map, filter and forEach, thus avoiding the issue altogether. This also automatically gives you scope loop bodies which is also quite useful.

If using those doesn't match the use case I usually resort to top declaration, simply so I can avoid the issues you mentioned.

Exelian
  • 5,749
  • 1
  • 30
  • 49
  • Those are built in? Not from a library? – Paul Phillips May 29 '12 at 15:26
  • Those would be awesome to use but in my project we don't use plugins and we have to support IE7, so `for (var i=0; i<100; i++) {}` all the way! :) – Ilya Tsuryev May 29 '12 at 15:29
  • 1
    @PaulPhillips, yup, from Javascript 1.6 they're built in. The MSDN also provides implementations if you have to support other browsers, see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array – Exelian May 30 '12 at 06:49
  • 2
    "Javascript 1.6"? If you're like most developers, you're writing for browsers, not for JavaScript versions, so what you actually need to know is: No, IE8 and earlier do not support any of `forEach`, `map`, and `filter`. – Val Kornea Aug 20 '14 at 08:41