Iteratory (Symbol.iterator)

Protokół iteracji: obiekt z metodą [Symbol.iterator]() działa z for...of, spreadem i destrukturyzacją.

Na tej stronie

Przykład

#
JavaScript
const tasks = ['Quiz', 'Projekt']
const iterator = tasks[Symbol.iterator]()

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
Konsola
{ value: 'Quiz', done: false }
{ value: 'Projekt', done: false }
{ value: undefined, done: true }

Tak właśnie działa pętla for...of: pobiera iterator i wywołuje next(), dopóki nie dostanie done: true.

Definicja i zastosowanie

#

Iteracja w JavaScripcie opiera się na prostej umowie. Obiekt iterowalny ma metodę o kluczu Symbol.iterator, która zwraca iterator. Iterator to obiekt z metodą next(), zwracającą przy każdym wywołaniu wynik { value, done }: kolejną wartość i informację, czy to już koniec.

Z tej umowy korzystają for...of, spread [...x], destrukturyzacja tablicowa, Array.from(), konstruktory Map i Set oraz Promise.all(). Tablice, napisy, Map, Set i listy NodeList są iterowalne od razu. Zwykły obiekt {} nie jest, dlatego for...of na nim rzuca TypeError.

Własny obiekt lub klasę uczynisz iterowalnymi, dodając metodę [Symbol.iterator](). Najprościej zapisać ją jako generator, *[Symbol.iterator]() {}, bo generatory same tworzą poprawne iteratory. Przeglądarki wydane od 2025 roku mają też metody pomocnicze iteratorów, jak map(), filter(), take() i toArray(), które przetwarzają wartości leniwie, bez tworzenia tablic pośrednich.

Składnia

#
Składnia
const iterator = iterable[Symbol.iterator]()
iterator.next()

class Name {
  *[Symbol.iterator]() {
    yield value
  }
}

Więcej przykładów

#
Własna klasa iterowalna
JavaScript
class Course {
  constructor(title, lessons) {
    this.title = title
    this.lessons = lessons
  }

  *[Symbol.iterator]() {
    for (const lesson of this.lessons) {
      yield `${this.title}: ${lesson}`
    }
  }
}

const course = new Course('CSS', ['Selektory', 'Flexbox', 'Grid'])

for (const lesson of course) {
  console.log(lesson)
}
console.log([...course].length)
Konsola
CSS: Selektory
CSS: Flexbox
CSS: Grid
3
Iterator zapisany ręcznie
JavaScript
const countdown = {
  from: 3,
  [Symbol.iterator]() {
    let current = this.from
    return {
      next() {
        if (current > 0) {
          return { value: current--, done: false }
        }
        return { value: undefined, done: true }
      }
    }
  }
}

console.log([...countdown])
const [first, second] = countdown
console.log(first, second)
Konsola
[3, 2, 1]
3 2

Ten sam obiekt działa ze spreadem i z destrukturyzacją, bo obie konstrukcje wywołują [Symbol.iterator]() i pobierają wartości przez next().

Pułapka: zwykły obiekt nie jest iterowalny
JavaScript
const scores = { Ania: 120, Kuba: 95 }

try {
  for (const score of scores) {
    console.log(score)
  }
} catch (error) {
  console.log(error.name)
}

console.log(typeof scores[Symbol.iterator])
console.log(typeof [][Symbol.iterator])

for (const [name, points] of Object.entries(scores)) {
  console.log(name, points)
}
Konsola
TypeError
undefined
function
Ania 120
Kuba 95

Obsługa przeglądarek

#

Szeroko dostępne

Działa we wszystkich nowoczesnych przeglądarkach, także na telefonach. Możesz używać bez obaw.

  • Chrome
  • Edge
  • Firefox
  • Safari

Dobre praktyki

#
  • Metodę [Symbol.iterator]() najłatwiej napisać jako generator z yield. Nie musisz wtedy ręcznie budować obiektów { value, done }.
  • Obiekt iterowalny, np. tablica, tworzy nowy iterator przy każdej pętli, więc można po nim przechodzić wielokrotnie. Sam iterator albo generator po wyczerpaniu nie zwróci już żadnej wartości.
  • Czy wartość da się przejść pętlą for...of, sprawdzisz warunkiem typeof value?.[Symbol.iterator] === "function".

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.