An iterable is anything that can hand out its values one at a time, in order. Arrays, strings, Sets, and Maps are all iterables. That shared ability is why the same tools, for...of, spread ..., and Array.from, work on all of them.
| Iterable | Not iterable |
|---|---|
| Array | Plain object { } |
| String | Number |
| Set, Map | WeakSet, WeakMap |
| Typed arrays, NodeList | null, undefined |
const person = { name: "Ava", age: 16 };
for (const x of person) { } // TypeError: person is not iterable
A plain object has no single obvious order for "its values," so JavaScript does not guess. You choose: Object.keys, Object.values, or Object.entries, each of which returns an iterable array.
Two entries in the table may be new. Typed arrays (like Uint8Array) are arrays that hold only one kind of number, used for things like image and sound data. A NodeList is the list of page elements you get back from document.querySelectorAll(), a web page feature outside this course. Both work with for...of.
Iterating means stepping through a sequence one element at a time. Here is for...of on each of the four main iterables. Watch what each one hands you:
for (const x of "Hey") console.log(x); // characters
for (const x of [2, 4]) console.log(x); // elements
for (const x of new Set(["a", "b"])) console.log(x); // values
const fruits = new Map([["apples", 500], ["bananas", 300]]);
for (const x of fruits) console.log(x); // [key, value] pairs
A Map is the one that surprises people: each step gives a two-element array. Destructure it with for (const [fruit, qty] of fruits) to get the parts by name.
Once something is iterable, all of these work on it:
const word = "code";
for (const ch of word) { } // for...of
[...word] // spread: ["c", "o", "d", "e"]
Array.from(word) // ["c", "o", "d", "e"]
new Set(word) // Set {"c", "o", "d", "e"}
const [first, second] = word; // destructuring: "c", "o"
Math.max(...new Set([3, 9, 4])) // 9
Some characters, like most emoji, take up two slots internally. Indexing and length see those two slots; iteration sees one real character:
const s = "hi😀";
console.log(s.length); // 4 (the emoji counts as 2)
console.log([...s].length); // 3 (h, i, 😀)
console.log(s[2]); // "\ud83d" half an emoji: garbage
console.log([...s][2]); // "😀"
[...text].length or [...text].reverse().join("").
Arrays, Sets, and Maps can each give you three different iterables:
const colors = ["red", "green"];
[...colors.keys()] // [0, 1]
[...colors.values()] // ["red", "green"]
[...colors.entries()] // [[0, "red"], [1, "green"]]
Every iterable has a special method stored under the key Symbol.iterator. That method is what for...of actually calls behind the scenes (the next lesson shows it in action):
function isIterable(x) {
return x != null && typeof x[Symbol.iterator] === "function";
}
isIterable([1, 2]) // true
isIterable("text") // true
isIterable(new Map()) // true
isIterable({ a: 1 }) // false
isIterable(42) // false
Which of these is not iterable?
"hello"new Set([1, 2]){ a: 1, b: 2 }new Map()for...of on one throws TypeError: ... is not iterable. Use Object.entries(obj) to get an iterable array of its pairs.What does this log?
const m = new Map([["x", 1]]);
for (const item of m) console.log(item);
["x", 1]. Looping over a Map gives [key, value] pairs, not just the values.Why does [..."hi😀"].length give 3 when "hi😀".length gives 4?
length counts the emoji's two internal slotslength counts the quote markslength see both slots; iterating (spread, for...of) gives the emoji as one character.Object.entries.length and [...s].length for a string with two emojiisIterable example with its Try it Yourself button and test six different values, predicting each result first