One might expect the following to print out a
, b
, c
.
var i, rowName;
for (i = 0; i < 3; i++, rowName = ['a', 'b', 'c'][i]) {
console.log(rowName);
}
Instead, however, it prints out undefined
, b
, c
. Why?
To clarify: I know how to make this work; what I'm curious about is why the above doesn't work.
One might expect the following to print out a
, b
, c
.
var i, rowName;
for (i = 0; i < 3; i++, rowName = ['a', 'b', 'c'][i]) {
console.log(rowName);
}
Instead, however, it prints out undefined
, b
, c
. Why?
To clarify: I know how to make this work; what I'm curious about is why the above doesn't work.
Share Improve this question edited Nov 13, 2015 at 1:54 royhowie 11.2k14 gold badges53 silver badges67 bronze badges asked Nov 13, 2015 at 1:29 MarkMark 12.6k6 gold badges24 silver badges38 bronze badges 3-
3
The final expression is only evaluated at the end of each loop iteration.
rowName
is undefined before then. See for @ MDN – showdev Commented Nov 13, 2015 at 1:36 -
1
The 3rd (and 2nd) expression in the
for
loop isn't evaluated until the end of each iteration. If it were evaluated upfront to assignrowName
, it would also immediately incrementi
to1
, skipping the initial value of0
. – Jonathan Lonowski Commented Nov 13, 2015 at 1:37 - The documentation could hardly be clearer. It says "final-expression An expression to be evaluated at the end of each loop iteration. This occurs before the next evaluation of condition. Generally used to update or increment the counter variable." – user663031 Commented Nov 13, 2015 at 2:45
4 Answers
Reset to default 5The reason it prints undefined
, b
, c
is because of how a for loop works.
for (initialization; condition; final expression)
Let's break down your for loop.
initialization: i = 0
condition: i < 3
final expression: i++, rowName = ['a', 'b', 'c'][i]
When your loop is first entered, i
is set to 0
. This is the initialization step. Then the condition step, i < 3
, is checked. This is done before every iteration to decide whether or not to continue looping. After each loop, the final expression is evaluated. In your example, you increment i
before setting rowName
equal to an element in ['a', 'b', 'c']
based on the current index.
In your case, on the first iteration, rowName
is undefined
because the final expression is yet to be evaluated. Every iteration thereafter behaves as you would expect since a final expression has already been previously evaluated.
If you want to do the tricky one-line for loop style, the "correct" syntax is:
var array = ['a', 'b', 'c'];
for (var i = 0, rowName; rowName = array[ i++ ]; ) {
console.log(rowName);
}
Notice the ending ;
of the for
loop declaration. There's technically an empty statement after the ;
which is where you normally would do i++
.
In this case, the "condition" of the for loop is taking advantage of the Javascript assignment operator. If you have some code like this:
var a;
if( a = 1 ) { // Note this is assignment = not parison ==
console.log('true');
}
It will log "true". Why? Because inside of an expression, a = 1
actually returns 1
. And 1
is "truthy," meaning it evaluates to true in a boolean context like an if
statement.
The opposite is also true, if your value is falsey:
var a;
if( a = 0 ) {
console.log('true');
}
It will not log, because a = 0
is returning 0 (as well as assigning 0 to a
). And 0 is falsey.
This whacky for-loop syntax is only for certain conditions:
- If any of the array elements are "falsey" (
null
,undefined
,""
, etc) it will prematurely terminate the loop, because of how operators work as mentioned above. - This assumes you don't care about the loop index
i
. It will be off by 1 for every iteration of the loop, becausei++
is executed before thefor
block. That is, the first time yourfor
body executes,i
will be 1, not its declared starting value of 0. - The only benefit of this pattern is saving a few bytes. It's generally not used in the real world because of the above two pitfalls.
I think you want this?
var i, rowName;
for (i = 0; i < 3; i++){
rowName = ['a', 'b', 'c'][i];
console.log(rowName);
}
You are reassigning rowName
at each step of the loop and it's undefined
to begin with. Here's what you can do:
for(var i=0,rowName=['a','b','c'],l=rowName.length; i<l; i++){
console.log(rowName[i]);
}
or something like:
var rowName = ['a', 'b', 'c'];
for(var i=0,l=rowName.length; i<l; i++){
console.log(rowName[i]);
}
The real issue is that the third condition inside for(assign; test; execute)
does not execute
until until one test
of the loop is satisfied. If the test
fails execute
never happens. If the test
passes execute
really begins after the first pass of the loop.