Modern JavaScript: For-of Loops
Welcome to the For-of Loops lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
JavaScript has a
for...inloop that can iterate over object keys (not values), arrays, and strings.>
const obj = {'a': 1,'b': 2,};const keys = [];for (const key in obj) {keys.push(key);}keys;Result:
['a', 'b']
This seems straightforward at first, but things can get confusing in some situations. One notorious problem occurs when looping over arrays.
Unlike most other languages, JavaScript's arrays are a special kind of JavaScript object.
typeof someArrayis'object', not'array'!>
typeof [1, 2, 3];Result:
'object'
We can perform any object operation on arrays. For example, we can ask for their keys. The "keys" of
['apple', 'banana']are['0', '1']: the array's indexes are converted into strings.>
const fruits = ['apple', 'banana', 'carrot'];Object.keys(fruits);Result:
['0', '1', '2']
We get the same result if we use a
for... inloop on the array. It iterates over object keys (not values). Because arrays are objects, we once again get each of the array's indexes as a string like'0'.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const elements = [];for (const fruit in fruits) {elements.push(fruit);}elements;Result:
['0', '1', '2']
Getting array indexes as strings is not very useful, but at least it's predictable. Next, we'll see examples where
for...inloops break down in more confusing ways.JavaScript arrays can be "sparse": they can have elements missing. Suppose that we create an empty array (
const numbers = []) and insert a value at index 3 (numbers[3] = 'a'). In most languages (Java, Python), that's an error. Some languages (Ruby) implicitly fill indexes 0 through 2 withnull,nil, or whatever other "not-a-value" value the language has.JavaScript does neither of those! That array has a value at index 3, but it has nothing at indexes 0, 1, or 2. This is an important point: the values at indexes 0-2 aren't
undefinedornull. The array doesn't have anything there at all.This exposes one of
for...inloops' edge cases. This loop skips missing array elements! If we loop over the array withfor...in, the keys 0-2 don't even show up. Only 3 shows up (as the string'3')!>
const letters = [];letters[3] = 'a';const keys = [];for (const key in letters) {keys.push(key);}keys;Result:
['3']
This unexpected behavior can cause very confusing bugs. When working with arrays, we write code that expects them to start at 0. In fact, "starts at index 0" is definitional for arrays in most languages, but not in JavaScript! (There are also similar problems with regular, non-array objects, but this array problem is much quicker to demonstrate.)
Fortunately, modern JavaScript provides a better type of loop:
for...of. It loops over the values in an array, not the keys. This mimics the "for-each" loops available in Java, Python, Ruby, and most other languages.>
const letters = ['a', 'b', 'c'];const result = [];for (const letter of letters) {result.push(letter);}result;Result:
['a', 'b', 'c']
The
for...ofloop is versatile: we can adapt it to address the problems we ran into with thefor...inloop.Let's start with a sparse array (an array with some missing indexes). Here, the
for...ofloop behaves as we'd expect. It iterates from index 0 until the highest index in the array. If an index is missing, the loop gives usundefined.In the example below, the loop body executes four times, giving us
undefinedthe first three times.(Remember that
for...ofreturns values, not keys!)>
const numbers = [];numbers[3] = 'a';const result = [];for (const n of numbers) {result.push(n);}result;Result:
[undefined, undefined, undefined, 'a']
What if we want to iterate over an object's keys, like
for...indoes? Fortunately, since 2011 JavaScript has had anObject.keysmethod that gives us an object's keys as an array. Then we canfor...ofover that array.>
const obj = {a: 1, b: 2};const keys = [];for (const key of Object.keys(obj)) {keys.push(key);}keys;Result:
['a', 'b']
And what if we want to iterate over a string? Once again,
for...ofdoes what you'd expect! The loop body executes once per character in the string. JavaScript doesn't have a dedicated character type, so the individual characters will show up as strings of length 1, like'a'.>
const s = 'loop';const chars = [];for (const char of s) {chars.push(char);}chars;Result:
['l', 'o', 'o', 'p']
for...ofloops also work well with other modern JavaScript features like iterators and generators. We'll see concrete examples when we discuss those topics.As is often the case, linters can stop you from accidentally using the old
for...insyntax. ESLint's guard-for-in rule will stop you from accidentally usingfor...inwhen you meanfor...of. (But it will still allowfor...inif you use certain patterns to make it safe.)