4

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.

3
  • 3
    The final expression is only evaluated at the end of each loop iteration. rowName is undefined before then. See for @ MDN 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 assign rowName, it would also immediately increment i to 1, skipping the initial value of 0. 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." Commented Nov 13, 2015 at 2:45

4 Answers 4

5

The 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.

Sign up to request clarification or add additional context in comments.

6 Comments

"In your example, you increment i before setting rowName". True, but what does incrementing i have to do with assignment of rowName? Misleading.
@PHPglue How is that misleading? I did not say either has anything to do with the other. However, both statements happen to be "final expressions," or "post-loop operations", and are hence both evaluated after each loop.
It might confuse OP into thinking that matters.
@PHPglue Into thinking what matters?
Incrementing i before setting rowName has nothing to do with the fact that the third part of the for loop doesn't execute until the first pass is complete. It just makes i start with 1 on the second pass.
|
2

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 comparison ==
    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, because i++ is executed before the for block. That is, the first time your for 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.

Comments

0

I think you want this?

var i, rowName;
for (i = 0; i < 3; i++){
    rowName = ['a', 'b', 'c'][i];
    console.log(rowName);
}

1 Comment

Because the assignment to rowname happens after each execution of the loop. So the first time through, rowname has not been assigned, hence you get undefined. (You could move the assignment to the "middle" expression in your for loop header.)
0

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.

1 Comment

The OP's snippet is attempting to log each value individually. While this would log the array, in its entirety, each iteration.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.