Hidden Quirks of JavaScript `for...in` Loops
The C-style three-expression for
loop in JavaScript is often viewed as old school or awkward beginner style. Some developers automatically use a for...of
, foreach
, or for...in
statement instead. But the latter has a subtle pitfall that can lead to unexpected behaviour.
Why are My List Indices Suddenly Strings?
This little piece of JavaScript code is supposed to convert a 0-based list of indices into a 1-based list:
const zeroBased = ['foo', 'bar', 'baz'];
for (const i in zeroBased) {
console.log(`${i + 1}`);
}
But the output may not be what you expect:
01
11
21
It becomes clearer, if you slightly change the code:
const zeroBased = ['foo', 'bar', 'baz'];
for (const i in zeroBased) {
console.log(`${i + 1} (${typeof i})`);
}
Output
01 (string)
11 (string)
21 (string)
It looks like all indices have been converted to strings and the +
operator concatenates instead of adding numerically.
But this explanation is not quite correct. In fact, no index gets converted to a string. Arrays in JavaScript are just objects and object keys are always strings. You can even mix string keys with numerical keys in JavaScript:
const arr = ['foo', 'bar', 'baz'];
arr['four'] = 'huh?';
console.log(arr['four']); // Output 'huh?'.
console.log(Array.isArray(arr)); // Output `true`.
console.log(arr); // Output `[ 'foo', 'bar', 'baz', four: 'huh?' ]`
In reality, JavaScript arrays differ from objects only in that they have additional methods like map()
, filter()
, and so on. The differences look bigger than they are because functions like JSON.stringify()
, console.log()
, console.dir()
and friends serialize or visualize arrays and non-array-objects differently.
Many times, the subtle problems stemming from this go unnoticed because code that ignores them just works as intended. Take this example:
const zeroBased = ['foo', 'bar', 'baz'];
for (const i in zeroBased) {
console.log(zeroBased[i]);
}
Output
foo
bar
baz
The variable i
is still a string. But when used as an Array
index, it is automatically coerced into a number.
Conclusion
If you want to access array indices, use the classic 3-expression form for
loop! It may be less concise but it is more explicit. And it is more efficient if you need the indices as numbers because it saves you from converting the strings back into number with parseInt()
which is ugly and a waste of CPU cycles.
Leave a comment