Iterator

What is an iterator?

Iterator is one such mechanism. It is an interface that provides a unified access mechanism for various data structures. As long as any data structure deploys the Iterator interface, it can complete the traversal operation (that is, process all members of the data structure in sequence).

There are three functions of Iterator: 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… of” loop, Iterator interface is mainly for “for… of” consumption.

An iterator is a special object with a next () method that returns a result object every time next () is called. This object has two Attributes - Value Pair, value and done, value is the result returned by the iterator each time, and done marks whether the iterator has ended. We can simulate an iterator using es5 syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 traversal process of Iterator looks like this.

Create a pointer object that points to the starting position of the current data structure. That is to say, the traverser object is essentially a pointer object.

The first call to the next method of a pointer object points the pointer to the first member of the data structure.

The second time the’next 'method of the pointer object is called, the pointer points to the second member of the data structure.

Keep calling the’next 'method of the pointer object until it points to the end position of the data structure.

Each call to the’next ‘method returns information about the current member of the data structure. Specifically, it returns an object containing two attributes,’ 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 has ended.

The above code defines a’makeIterator ‘function, which is a traverser-generated function that returns a traverser object. Executing this function on the array’ [‘a’, ‘b’] 'returns the traverser object (i.e. 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 representing information about the current data member. This object has two properties,’ value ‘and’done’. The’value ‘property returns the member at the current position, and the’done’ property is a boolean value indicating whether the traversal has ended, that is, whether it is necessary to call the’next 'method again.

In short, calling the’next 'method of the pointer object allows you to traverse the given data structure.

Since Iterator just adds the interface specification to the data structure, the traverser is actually separate from the data structure it traverses, and it is completely possible to write a traverser object without a corresponding data structure, or use a traverser object. Simulate the data structure.

Iterable objects

In es6, all collection objects (arrays, sets, maps) and strings are iterables, and iterables are bound to default iterators, which we can use using es6’s new syntax’for-of '.
The’for-of ‘loop, which can be used on iterables, takes advantage of the default iterator on iterables. The approximate process is: the’for-of’ loop will call the next () method of the iterable every time it is executed, and store the value property of the result object returned by the iterator in a variable, and the loop will continue to execute this process until the value of the returned object’s’done ‘property is’true’.

Accessing the default iterator

Iterable objects all have a Symbol.iterator method (if you don’t know what Symbol is, you can take a look.)Symbol), the’for-of ‘loop, by calling the Symbol.iterator method of the’colors’ array to get the default iterator, this process is done behind the’JavaScript 'engine.

We can take the initiative to get this default iterator to feel it:

1
2
3
4
5
6
7
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefined, done: true}"

In this code, you get the default iterator for array values via Symbol.iterator and use it to iterate through the elements in the array. Executing a for-of loop statement in a JavaScript engine is a similar process.

Use the Symbol.iterator property to detect whether an object is an iterable:

1
2
3
4
5
6
7
8
function isIterator(object) {
return typeof object[Symbol.iterator] = "function";
}

console.log(isIterable([1, 2, 3])); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new Map())); // true
console.log(isIterable("Hello")); // true

Create iterable objects

When we are creating an object, we can add a generator to the Symbol.iterator property to turn it into an iterable object.

There is no need to worry about traversing the iterator when for-of.

You can see that our custom iterable object is actually a function with a key value of Symbol.iterator, which is using the default iterator of the array items.

When we call for-of on the collection, we are actually calling the Symbol.iterator function of the collection.

** To sum up, in fact, whether an object is iterable is to see if it has a Symbol.iterator function child object, by default array, set, string have, so they can directly use for-of, but when we give an object When adding Symbol.iterator function, it can also call for-of **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let collection = {
items: [],
* [Symbol.iterator ]() { // assign a generator to the Symbol.iterator property of an object to create a default iterator
for(let item of this.items) {
yield item;
}
}
};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for(let x of collection) {
console.log(x);
}

Built-in iterator

The collection objects in ES6, Array, Set, and Map, all have three built-in iterators:

  • entries () returns an iterator with the value of multiple Attribute-Value Pairs.
    If it is an array, the first element is the index position; if it is a’Set 'collection, the first element, like the second element, is a value.
  • values () returns an iterator whose value is the value of the collection.
  • keys () returns an iterator with the values of all the key names in the collection.
    If it is an array, the index is returned; if it is a’Set ‘collection, the value is returned (the value of’Set’ is used as both a key and a value).

Default iterator

Each collection type has a default iterator. In the for-of loop, if it is not explicitly specified, the default iterator is used. According to general usage habits, we can easily guess that the default iterator for arrays and’Set ‘collections is values (), and the default iterator for’Map’ collections is entries ().

When calling iterators

The Iterator interface (the Symbol.iterator method) is called by default in some cases, in addition to the for… of loop described below, and several others.

** Destruct assignment **

When destructuring and assigning values to array and Set structures, the Symbol.iterator method is called by default.

1
2
3
4
5
6
7
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'];

** Extension operator **

The extension operator (…) also calls the default Iterator interface.

1
2
3
4
5
6
7
8
//Example 1
var str = 'hello';
[...str] // ['h','e','l','l','o']

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

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

In fact, this provides a convenient mechanism for converting any data structure with the Iterator interface deployed into an array. That is, as long as a data structure has the Iterator interface deployed, it can be converted into an array using extension operators.

1
let arr = [...iterable];

yield*

'Yield * 'is followed by a traversable struct that calls its traverser interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 }

** Other Occasions **

Since the traversal of an array invokes the traverser interface, any case where an array is accepted as a parameter actually invokes the traverser interface. Here are some examples.

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

Advanced Features of Iterators

Pass arguments to the iterator

As we saw earlier, using the yield keyword inside the iterator can generate a value, and outside the iterator’s next () method can be used to obtain the return value.
In fact, the next () method can also receive parameters, and the value of this parameter will replace the return value of the previous yield statement inside the generator.

1
2
3
4
5
6
7
8
9
10
11
12
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}

let iterator = createIterator();

console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

Of the traverser object

In addition to having a’next ‘method, a traverser object can also have a’return’ method and a’throw ‘method. If you write your own traverser object to generate a function, then the’next’ method must be deployed, and the deployment of the’return ‘method and the’throw’ method is optional.

The’return ‘method is used when the’for… of’ loop exits prematurely (usually due to an error, or a’break ‘statement). The’return’ method can be deployed if an object needs to clean up or free resources before completing the traversal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}

In the above code, the function’readLinesSync ‘takes a file object as a parameter and returns a traverser object, which deploys the’return’ method in addition to the’next ‘method. In the following two cases, the’return’ method will be triggered.

1
2
3
4
5
6
7
8
9
10
11
//Situation 1
for (let line of readLinesSync(fileName)) {
console.log(line);
break;
}

//Scenario 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, the’return ‘method will be executed to close the file; the second situation will throw an error after the’return’ method is executed to close the file.

Note that the’return 'method must return an object, which is determined by the Generator specification.

The’throw 'method is mainly used in conjunction with the Generator function. General traverser objects do not use this method.

What is a Generator?

The generator is actually the encapsulated syntactic sugar provided by the es6 syntax. Its effect is roughly the same as the iterator we just simulated, but with the generator, we don’t have to implement the iterator ourselves every time.

A generator is a function that returns an iterator, indicated by an asterisk (*) after the keyword function, which uses the new keyword yield.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function *createIterator(items) {
for(let i=0; i<items.length; i++) {
yield items[i];
}
}

let iterator = createIterator([1, 2, 3]);

//Since the generator returns an iterator, it is natural to call the next () method of the iterator
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
All subsequent calls will return the same content
console.log(iterator.next()); // "{ value: undefiend, done: true}"

Above, we use the generator of’ES6 ‘to greatly simplify the creation process of iterators. We pass an array of items to the generator function createIterator (). Inside the function, the for loop continuously generates new elements from the array and puts them into the iterator. The loop stops every time a’yield’ statement is encountered; every time the iterator’s next () method is called, the loop continues to run and stops at the next’yield 'statement.

How generators are created

A generator is a function:

1
function *createIterator(items) { ... }

It can be written as a function expression:

1
let createIterator = function *(item) { ... }

Can also be added to the object, ‘ES5’ style object literal:

1
2
3
4
5
let o = {
createIterator: function *(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

'ES6 'style object method shorthand:

1
2
3
4
5
let o = {
*createIterator(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

Reference link:

https://es6.ruanyifeng.com/#docs/generator

https://segmentfault.com/a/1190000010747122

https://es6.ruanyifeng.com/#docs/iterator