Uczymy się, czym są iteratory w JavaScript i jak z nich korzystać. Do dzieła!

Na początek funkcja, która sprawdza, czy obiekt ma iterator:

function hasIterator(obj) {
    return obj && typeof obj[Symbol.iterator] === "function";
}

Teraz bardzo prosta klasa:

class Collection {

    constructor(arr) {
        this.arr = arr;
    }

}

let items = new Collection([1,2,3,4,5]);
console.log(hasIterator(items));
//false

Jak widać nie posiada iteratora. Napiszmy go:

class Collection {

    constructor(arr) {
        this.arr = arr;
    }

    [Symbol.iterator]() {
       let items = this.arr;
       let index = 0;

        return {
            next: function() {
                return {
                    done: (index === items.length) ? true : false,
                    value: items[index++]
                };
            }
        };
    }

}

Logika jest prosta – funkcja next zwraca done i value. Indeksy danego iteratora są pamiętane za pomocą closure.

Możemy to przetestować:

let items = new Collection([1,2,3,4,5]);
console.log(hasIterator(items));
//true

for(let el of items) {
    console.log(el);
}

// 1
// 2
// 3
// 4
// 5

Iterator możemy napisać inaczej, choć pewnie nie zrozumiemy jak to działa, jeżeli nie wiemy, czym są generatory:

function hasIterator(obj) {
    return obj && typeof obj[Symbol.iterator] === "function";
}

class Collection {

    constructor(arr) {
        this.arr = arr;
    }

    *[Symbol.iterator]() {
        yield *this.arr;
    }

}

let items = new Collection([1,2,3,4,5]);
console.log(hasIterator(items));
//true

for(let el of items) {
    console.log(el);
}

// 1
// 2
// 3
// 4
// 5

„Jak” jest tutaj mało ważne, bo jest to świetny patent na tworzenie iteratorów na zasadzie kopiuj-wklej, ale o generatorach niebawem też sobie powiemy.