Iterator and for...of

Loop

Iterator (traverser) concept

JavaScript’s original data structure representing "collection", mainly arrays (Array) and objects (Object), ES6 added Map and Set. In this way, there are four types of data sets, and users can combine them to define their own data structure. For example, the members of the array are Map, and the members of Map are objects. This requires a unified interface mechanism to handle all different data structures.

Iterator is such a mechanism. It is an interface that provides a unified access mechanism for a variety of different data structures. Any data structure can complete the traversal operation (that is, process all the members of the data structure in turn) as long as the Iterator interface is deployed.

Iterator has three functions: one is to provide a unified and convenient access interface for various data structures; the other is to enable the members of the data structure to be arranged in a certain order; the third is that ES6 creates a new traversal command. for...ofloop, the Iterator interface is mainly used for consumption byfor...of`.

The traversal process of Iterator is like this.

(1) Create a pointer object that points to the starting position of the current data structure. In other words, the iterator object is essentially a pointer object.

(2) The first time you call the next method of the pointer object, you can point the pointer to the first member of the data structure.

(3) The second call to the next method of the pointer object, the pointer points to the second member of the data structure.

(4) Keep calling the next method of the pointer object until it points to the end of the data structure.

Each time the next method is called, the information of the current member of the data structure will be returned. Specifically, it returns an object containing two attributes of value and done. Among them, the value attribute is the value of the current member, and the done attribute is a boolean value indicating whether the traversal is over.

The following is an example of simulating the return value of the next method.

var it = makeIterator(["a", "b"]);

it.next(); // {value: "a", done: false}
it.next(); // {value: "b ", done: false}
it.next(); // {value: undefined, done: true}

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function () {
      return nextIndex < array.length
        ? { value: array[nextIndex++], done: false }
        : { value: undefined, done: true };
    },
  };
}

The above code defines a makeIterator function, which is a iterator generating function, and its role is to return an iterator object. Executing this function on the array ['a','b'] will return the iterator object (pointer object) it of the array.

The next method of the pointer object is used to move the pointer. At the beginning, the pointer points to the beginning of the array. Then, every time the next method is called, the pointer will point to the next member of the array. The first call points to a; the second call points to b.

The next method returns an object that represents information about the current data member. This object has two attributes of value and done. The value attribute returns the member at the current position. The done attribute is a boolean value indicating whether the traversal is over, that is, whether it is necessary to call the next method again .

In short, by calling the next method of the pointer object, you can traverse the data structure given in advance.

For the iterator object, both the done: false and value: undefined properties can be omitted, so the above makeIterator function can be abbreviated as the following form.

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function () {
      return nextIndex < array.length
        ? { value: array[nextIndex++] }
        : { done: true };
    },
  };
}

Because Iterator only adds interface specifications to the data structure, the iterator and the data structure it traverses are actually separate, and it is possible to write the iterator object without a corresponding data structure, or say Use the iterator object to simulate the data structure. The following is an example of an endless running iterator object.

var it = idMaker(); it.next()

.value // 0
it.next().value // 1
it.next().value // 2
// ...

function idMaker() {
  var index = 0;

  return {
    next: function() {
      return {value: index++, done: false};
    }
  };
}
```In the

above example, the iterator generates the function `idMaker` and returns a iterator object (That is, the pointer object). But there is no corresponding data structure, in other words, the traverser object describes a data structure by itself.

If you use TypeScript, the specification of iterable interface (Iterable), pointer object (Iterator), and the return value of the `next` method can be described as follows.

```javascript
interface Iterable {
  [Symbol.iterator](): Iterator,
}

interface Iterator {
  next(value?: any): IterationResult,
}

interface IterationResult {
  value: any,
  done: boolean,
}

Default

Iterator interface The purpose of the Iterator interface is to provide one for all data structures The unified access mechanism is the for...of loop (see below for details). When using the for...of loop to traverse a certain data structure, the loop will automatically look for the Iterator interface.

As long as a data structure is deployed with the Iterator interface, we call this data structure "iterable".

ES6 stipulates that the default Iterator interface is deployed in the Symbol.iterator property of the data structure. In other words, as long as a data structure has the Symbol.iterator property, it can be considered "iterable". The Symbol.iterator property itself is a function, which is the default iterator generation function of the current data structure. Executing this function will return a iterator. As for the property name Symbol.iterator, it is an expression that returns the iterator property of the Symbol object. This is a predefined special value of type Symbol, so it should be placed in square brackets (see Chapter "Symbol").

const obj = {
  [Symbol.iterator]: function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};
```In the

above code, the object `obj` is iterable because it has `Symbol.iterator `Properties. Executing this property will return a iterator object. The fundamental feature of this object is the `next` method. Every time the `next` method is called, an information object representing the current member will be returned, with two attributes `value` and `done`.

Some ES6 data structures natively have the Iterator interface (such as arrays), that is, they can be traversed by `for...of` without any processing. The reason is that these data structures are natively deployed with the `Symbol.iterator` property (see below for details), while some other data structures do not (such as objects). Any data structure with the `Symbol.iterator` property deployed is called the iterator interface deployed. Calling this interface will return a traverser object.

The data structure with native Iterator interface is as follows.

- Array
- the Map
- the Set
- String
- TypedArray
- arguments The function of the object
- NodeList objects

The following example is the `Symbol.iterator` property of an array.

```javascript
let arr = ['a','b','c'];
let iter = arr[Symbol.iterator]();

iter.next() // {value:'a', done: false }
iter.next() // {value:'b', done: false}
iter.next() // {value:'c', done: false}
iter.next() // {value: undefined, done : true}
```In the

above code, the variable `arr` is an array, which has a iterator interface natively and is deployed on the `Symbol.iterator` property of `arr`. So, by calling this property, you get the iterator object.

For the data structure that natively deploys the Iterator interface, you don't need to write your own iterator generation function, the `for...of` loop will automatically traverse them. In addition, the Iterator interface of other data structures (mainly objects) needs to be deployed on the `Symbol.iterator` property by itself, so that it can be `for... of` loop traversal.

The reason why the Iterator interface is not deployed by default for Object is because which property of the object is traversed first, and which property is traversed later is uncertain, and the developer needs to manually specify it. Essentially, the traverser is a linear process. For any non-linear data structure, deploying the traverser interface is equivalent to deploying a linear transformation. However, strictly speaking, the object deployment traverser interface is not very necessary, because at this time the object is actually used as a Map structure, ES5 does not have a Map structure, and ES6 natively provides it.

If an object has an Iterator interface that can be called by `for...of` loops, it must deploy the iterator generation method on the property of `Symbol.iterator` (the object on the prototype chain can also have this method).

```javascript
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() {return this;}

  next() {
    var value = this.value;
    if (value <this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator( start, stop);
}

for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}
```The

above code is a way of writing the Iterator interface for a class deployment. The `Symbol.iterator` property corresponds to a function, which returns the iterator object of the current object after execution.

The following is an example of the pointer structure implemented by the iterator.

```javascript
function Obj(value) {
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
  var iterator = {next: next };

  var current = this;

  function next() {
    if (current) {
      var value = current.value;
      current = current.next;
      return {done: false, value: value };
    }
    return {done: true };
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i); // 1, 2, 3
}
```The

above code first Deploy the `Symbol.iterator` method on the prototype chain of the constructor, calling this method will return the iterator object `iterator`, call the object's `next` method, and automatically move the internal pointer down while returning a value An example.

Below is another example of adding an Iterator interface to an object.

```javascript
let obj = {
  data: ['hello','world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index <self. data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        }
        return {value: undefined, done: true };
      }
    };
  }
};

For array-like objects (there are numeric keys and length properties), there is an easy way to deploy the Iterator interface, which is Symbol.iterator The method directly refers to the Iterator interface of the array.

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// Or
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll( 'div')] // Can be executed
``` The

NodeList object is an array-like object, which originally has a traversal interface and can be traversed directly. In the above code, we changed its traversal interface to the `Symbol.iterator` property of the array, you can see that there is no effect.

The following is another example of an array-like object calling the `Symbol.iterator` method of an array.

```javascript
let iterable = {
  0:'a',
  1:'b',
  2:'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); //'a','b','c'
}

Note, The Symbol.iterator method of the normal object deployment array has no effect.

let iterable = {
  a: "a",
  b: "b",
  c: "c",
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator],
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}

If the Symbol.iterator method does not correspond to the iterator generation function (that is, it returns an iterator object), the interpretation engine will Report an error.

var obj = {};

obj[Symbol.iterator] = () => 1;

[...obj] // TypeError: [] is not a function
```In the

above code, the `Symbol.iterator` method of the variable `obj` does not correspond to the traverser generating function, so an error is reported.

With the iterator interface, the data structure can be traversed using `for...of` loops (see below for details), or it can be traversed using `while` loops.

```javascript
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
  var x = $result.value;
  // ...
  $result = $iterator.next();
}
```In the

above code, `ITERABLE` represents some kind of traversable data structure, and `$iterator` is its iterator object. Every time the traverser object moves the pointer (`next` method), it checks the `done` property of the return value. If the traversal has not ended, it moves the pointer of the traverser object to the next step (`next` method), and keeps looping. .

##

Where to call the Iterator interface There are some occasions where the Iterator interface (the `Symbol.iterator` method) is called by default. In addition to the `for...of` loop described below, there are several other occasions.

**(1) Destructuring Assignment** When destructuring and assigning

arrays and Set structures, the `Symbol.iterator` method will be called by default.

```javascript
let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

(2) spread operator

The spread operator (...) will also call the default Iterator interface.

// Example one
var str ='hello';
[...str] // ['h','e','l','l','o']

// Example two
let arr = ['b','c'];
['a', ...arr,'d']
// ['a','b','c','d'
] ```

The expansion operator in the above code calls the Iterator interface internally.

In fact, this provides a convenient mechanism to convert any data structure deployed with the Iterator interface into an array. In other words, as long as a certain data structure deploys the Iterator interface, you can use the spread operator on it to convert it into an array.

```javascript
let arr = [...iterable];

(3) yield*

What follows yield* is a traversable structure, which calls the traverser interface of the structure.

let generator = function* () {
  yield 1;
  yield* [2, 3, 4];
  yield 5;
};

var iterator = generator();

iterator.next(); // {value: 1, done : false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: 4, done: false }
iterator.next(); // {value: 5, done: false}
iterator.next(); // {value: undefined, done: true}

(4) Other occasions

due to array traversal The iterator interface is called, so any occasion that accepts an array as a parameter actually calls the iterator interface. Here are some examples. -for

...of -Array.from() -Map(), Set(), WeakMap(), WeakSet() (such as new Map([['a',1],['b',2] ])) -Promise.all() -Promise.race()

Iterator interface of

strings A string is an array-like object, and it also has an Iterator interface natively .

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next() // {value: "h", done : false}
iterator.next() // {value: "i", done: false}
iterator.next() // {value: undefined, done: true}
```In the

above code, call `Symbol.iterator` The method returns a iterator object, and the next method can be called on this iterator to realize the traversal of the string.

You can override the native `Symbol.iterator` method to modify the behavior of the iterator.

```javascript
var str = new String("hi");

[...str] // ["h", "i"]

str[Symbol.
    next: function() {
      if (this._first) {
        this._first = false;
        return {value: "bye", done: false };
      } else {
        return {done: true };
      }
    },
    _first: true
  };
};

[...str] // ["bye"]
str // "hi"
```In the

above code, the `Symbol.iterator` method of the string str has been modified, so the spread operator (`.. .`) The returned value becomes `bye`, and the string itself is still `hi`.

## Iterator interface and Generator function

`Symbol.iterator()` The simplest implementation is to use the Generator function that will be introduced in the next chapter.

```javascript
let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
};
[...myIterable] // [1, 2, 3]

// Or use the following concise writing

let obj = {
  * [Symbol.iterator]() {yield'hello
    '; yield'world
    ';
  }
};

for (let x of obj) {
  console.log(x);
}
// "hello"
// "world"
```In the

above code, the `Symbol.iterator()` method hardly needs to deploy any code, just use the yield command Just give the return value of each step.

## The return() and throw() of the

iterator object, in addition to the `next()` method, can also have the `return()` method and the `throw()` method. If you write your own traverser object generation function, the `next()` method must be deployed, and whether the `return()` method and the `throw()` method are deployed is optional.

The use case of the `return()` method is that if the `for...of` loop exits prematurely (usually because of an error or a `break` statement), the `return()` method will be called. If an object needs to clean up or release resources before completing the traversal, you can deploy the `return()` method.

```javascript
function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return {done: false };
        },
        return() {
          file.close();
          return {done: true };
        }
      };
    },
  };
}
`` `In the

above code, the function `readLinesSync` accepts a file object as a parameter and returns a iterator object. In addition to the `next()` method, the `return()` method is also deployed. The following two situations will trigger the execution of the `return()` method.

```javascript
// Case 1
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

// Case 2
for (let line of readLinesSync(fileName)) {
  console.log(line) ;
  throw new Error();
}
In the

above code, after the first line of the output file is output, the `return()` method will be executed to close the file; in the second case, an error will be thrown after the file is closed by the `return()` method. .

Note that the `return()` method must return an object, which is determined by the Generator syntax.

The `throw()` method is mainly used in conjunction with the Generator function. This method is not used by general traverser objects. Please refer to the chapter "Generator Functions".

## for...of loop

ES6 draws on C++, Java, C# and Python language, and introduces the `for...of` loop as a unified method for traversing all data structures.

As long as a data structure is deployed with the `Symbol.iterator` property, it is considered to have an iterator interface, and you can use `for...of` to loop through its members. In other words, the `for...of` loop calls the `Symbol.iterator` method of the data structure.

The range that the `for...of` loop can use includes arrays, Set and Map structures, some array-like objects (such as the `arguments` object, DOM NodeList object), the Generator object described below, and strings.

### Array

Arrays natively have the `iterator` interface (that is, the `Symbol.iterator` property is deployed by default). The `for...of` loop is essentially the iterator generated by calling this interface, which can be proved by the following code.

```javascript
const arr = ['red','green','blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(let v of obj) {
  console.log( v); // red green blue
}
```In the

above code, the empty object `obj` deploys the `Symbol.iterator` property of the array `arr`, resulting in the `for...of` loop of `obj`, which produces The result is exactly the same as `arr`.

The `for...of` loop can replace the `forEach` method of the array instance.

```javascript
const arr = ['red','green','blue'];

arr.forEach(function (element, index) {
  console.log(element); // red green blue
  console.log(index) ; // 0 1 2
});

JavaScript's original for...in loop can only get the key name of the object, not the key value directly. ES6 provides a for...of loop, allowing traversal to obtain key values.

var arr = ['a','b','c', '

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // abcd
}
```The

above code shows that `for ...in` reads key names in a loop, `for...of` reads key values ​​in a loop. If you want to get the index of the array through the `for...of` loop, you can use the `entries` method and the `keys` method of the array instance (see the chapter "Expansion of Arrays").

`for...of` calls the traverser interface in a loop, and the traverser interface of the array only returns attributes with numerical indexes. This is different from the `for...in` loop.

```javascript
let arr = [3, 5, 7];
arr.foo ='hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2 ", "foo"
}

for (let i of arr) {
  console.log(i); // "3", "5", "7"
}
```In the

above code, `for... The of` loop will not return the `foo` property of the array `arr`.

### Set and Map structures

Set and Map structures also natively have an Iterator interface, so you can use the `for...of` loop directly.

ECMA-262 ```The
above code demonstrates how to traverse the Set structure and the Map structure. There are two points worth noting. First, the order of traversal is the order in which each member is added to the data structure. Secondly, when the Set structure is traversed, it returns a value, and when the Map structure is traversed, it returns an array. The two members of the array are the key name and key value of the current Map member.
```javascript



let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
  console.log(pair);
}
// ['a', 1 ]
// ['b', 2]

for (let [key, value] of map) {
  console.log(key + ':' + value);
}
// a: 1
// b: 2

Calculated data structure

Some data structures are calculated based on the existing data structure. For example, ES6 arrays, Sets, and Maps all deploy the following three methods, which all return the iterator object after the call.

-entries() returns an iterator object used to traverse the array composed of [keyname, keyvalue]. For arrays, the key name is the index value; for Set, the key name is the same as the key value. The Iterator interface of the Map structure, by default, calls the entries method. -keys() returns a iterator object used to iterate through all the keys. -values() returns a iterator object used to iterate over all key values.

The traversal object generated after these three method calls are all calculated data structures.

let arr = ['a','b','c'];
  console.log(pair);
}
// [0,'a']
// [1,'b']
// [2,'c']

Array-

like objects include array-like objects There are several types. The following are examples of for...of loops used for strings, DOM NodeList objects, and arguments objects.

// string
let str = "hello";

for (let s of str) {
  console.log(s); // hello
}

// DOM NodeList object
let paras = document.querySelectorAll("p") ;

for (let p of paras) {
  p.classList.add("test");
}

// arguments object
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs(' a','b');
//'a'

For strings, the `for...of` loop has another feature, that is, it will correctly recognize 32-bit UTF-16 characters.

```javascript
for (let x of'a\uD83D\uDC0A') {
  console.log(x);
}
//'a'
//'\uD83D\uDC0A'

Not all objects like arrays are With the Iterator interface, a convenient solution is to use the Array.from method to convert it to an array.

let arrayLike = { length: 2, 0: "a", 1: "b" };

// report an error
for (let x of arrayLike) {
  console.log(x);
}

// correct
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

Object

For ordinary objects, the for...of structure cannot be used directly, an error will be reported and the Iterator interface must be deployed Can be used later. However, in this case, the for...in loop can still be used to traverse the key names.

let es6 = {
  edition: 6,
  committee: "TC39",
  standard: "ECMA-262"
};

for (let e in es6) {
  console.log(e);
}
// edition
// committee
// standard

for (let e of es6) {
  console. log(e);
}
// TypeError: es6[Symbol.iterator] is not a function
```The

above code indicates that for ordinary objects, the `for...in` loop can traverse the key names, `for... of` loop will report an error.

One solution is to use the `Object.keys` method to generate an array of object keys, and then traverse the array.

```javascript
for (var key of Object.keys(someObject)) {
  console.log(key +': '+ someObject[key]);
}

Another method is to use the Generator function to repackage the object.

const obj = { a: 1, b: 2, c: 3 };

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

for (let [key, value] of entries(obj)) {
  console.log(key, "->", value);
}
// a -> 1
// b -> 2
// c -> 3

Comparison with other traversal syntax

Taking arrays as an example, JavaScript provides a variety of traversal syntax. The most primitive way of writing is the for loop.

for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

This writing method is more troublesome, so the array provides the built-in forEach method.

myArray.forEach(function (value) {
  console.log(value);
});

The problem with this way of writing is that it cannot jump out of the forEach loop, break command or return command Neither worked.

The for...in loop can traverse the keys of the array.

for (var index in myArray) {
  console.log(myArray[index]);
}
The;

for...in loop has several disadvantages.

-The key names of the array are numbers, but the for...in loop uses strings as the key names "0", "1", "2" and so on. -The for...in loop not only traverses the numeric key names, but also traverses other keys manually added, even the keys on the prototype chain. -In some cases, the for...in loop will iterate through the key names in any order.

In short, the for...in loop is mainly designed for traversing objects, not for traversing arrays.

The for...of loop has some significant advantages over the above methods.

for (let value of myArray) {
  console.log(value);
}
  • Has the same concise syntax as for...in, but without the disadvantages of for...in.
  • Unlike the forEach method, it can be used in conjunction with break, continue and return.
  • Provides a unified operation interface for traversing all data structures.

The following is an example of using the break statement to jump out of the for...of loop.

for (var n of fibonacci) {
  if (n> 1000)
    break;
  console.log(n);
}
```The

above example will output the Fibonacci number less than or equal to 1000. If the current item is greater than 1000, the `break` statement will be used to jump out of the `for...of` loop.