Iterator

Let's figure out how to iterate over elements of collections in the order we want.

Time to read: 5 min

Briefly

An iterator is an object that can access the elements of a collection one at a time while keeping track of its current position within that sequence.

In other words, an iterator is a mechanism that allows moving (iterating) through the elements of a collection in a certain order and makes them accessible.

How to Understand

An iterator in JavaScript is an object that returns the next element of a sequence through the next() method. This method returns an object with two properties:

  • value — the value of the current element of the collection.
  • done — an indicator that shows whether there are more values in the collection available for iteration.
        
          
          function makeIterator(array) {  let nextIndex = 0  return {    next: function () {      if (nextIndex < array.length) {        const result = { value: array[nextIndex], done: false }        nextIndex++        return result      } else {        return { done: true }      }    }  }}
          function makeIterator(array) {
  let nextIndex = 0

  return {
    next: function () {
      if (nextIndex < array.length) {
        const result = { value: array[nextIndex], done: false }
        nextIndex++
        return result
      } else {
        return { done: true }
      }
    }
  }
}

        
        
          
        
      

After creation, the iterator object can be used explicitly by calling the next() method:

        
          
          let iterator = makeIterator(['Hello', 'world'])console.log(iterator.next().value)// 'Hello'console.log(iterator.next().value)// 'world'console.log(iterator.next().done)// true
          let iterator = makeIterator(['Hello', 'world'])
console.log(iterator.next().value)
// 'Hello'
console.log(iterator.next().value)
// 'world'
console.log(iterator.next().done)
// true

        
        
          
        
      

Once the next() method finishes iteration, it returns { done: true }. This is a signal that the iteration is complete.

Why is it Needed

Almost everywhere iteration is needed, it is implemented through iterators. This includes not only strings and arrays, but also other data structures. Modern JavaScript has added a new concept of "iterable" objects, for instance, Map, introduced in ES6. This allows iterating over an iterable object in a for..of loop:

        
          
          for (let value of ['a', 'b', 'c']) {  console.log(value)  // a  // b  // c}
          for (let value of ['a', 'b', 'c']) {
  console.log(value)
  // a
  // b
  // c
}

        
        
          
        
      

Suppose we have an object person representing a dataset:

        
          
          const person = {  name: 'Mark',  age: 30,  gender: 'male',  interests: ['music', 'fishing'],}
          const person = {
  name: 'Mark',
  age: 30,
  gender: 'male',
  interests: ['music', 'fishing'],
}

        
        
          
        
      

To make such an object iterable and allow for..of to work with it, we define Symbol.iterator in it:

        
          
          person[Symbol.iterator] = function () {  const properties = Object.keys(this)  let count = 0  return {    next() {      if (count < properties.length) {        const key = properties[count]        let result = { done: false, value: person[key] }        count++        return result      } else {        return { done: true }      }    },  }}
          person[Symbol.iterator] = function () {
  const properties = Object.keys(this)
  let count = 0

  return {
    next() {
      if (count < properties.length) {
        const key = properties[count]
        let result = { done: false, value: person[key] }
        count++
        return result
      } else {
        return { done: true }
      }
    },
  }
}

        
        
          
        
      

Let's make sure that the person object is indeed iterable:

        
          
          for (let x of person) {  console.log(x)  // Mark, 30, male, ['music', 'fishing']}
          for (let x of person) {
  console.log(x)
  // Mark, 30, male, ['music', 'fishing']
}

        
        
          
        
      

Built-in Iterators

In some cases, the iterator interface is called by default. Such objects as String, Array, Map and Set are iterable because their prototypes contain Symbol.iterator.

Where Else Iterators Are Found

Destructuring

When destructuring, the iterator is used to access elements of a collection:

        
          
          const [a, b] = new Set(['a', 'b', 'c'])// a// b
          const [a, b] = new Set(['a', 'b', 'c'])
// a
// b

        
        
          
        
      

Array.from()

Array.from() allows converting an iterable object into an array:

        
          
          const arr = Array.from(new Set(['a', 'b', 'c']))// ['a', 'b', 'c']
          const arr = Array.from(new Set(['a', 'b', 'c']))
// ['a', 'b', 'c']

        
        
          
        
      

Spread Operator

The spread operator also calls the iterator interface by default:

        
          
          const arr = [...new Set(['a', 'b', 'c'])]// ['a', 'b', 'c']
          const arr = [...new Set(['a', 'b', 'c'])]
// ['a', 'b', 'c']

        
        
          
        
      

Map, Set

The constructors for Map and Set convert iterable values into Map and Set respectively:

        
          
          const map = new Map([  ['uno', 'one'],  ['dos', 'two'],])map.get('uno')// onemap.get('dos')// twoconst set = new Set(['red', 'green', 'blue'])set.has('red')// trueset.has('yellow')// false
          const map = new Map([
  ['uno', 'one'],
  ['dos', 'two'],
])
map.get('uno')
// one
map.get('dos')
// two

const set = new Set(['red', 'green', 'blue'])
set.has('red')
// true
set.has('yellow')
// false