• Scotland team
  • Author: aishery

What is an iterator

Iterators are a mechanism for uniformly traversing different collections by deploying the Iterator interface to the data structure that needs to be traversed, either by calling the interface or by using an API that consumes the interface.

Iterator pattern

Before we touch on iterators, let’s take a look at what the iterator pattern is and think about some examples from our own lives. When we need to buy tickets to visit scenic spots, the conductor needs to do something. He will sell tickets to everyone in line, including ordinary adults, students and children. Ticket sellers need to sell out the tickets of visitors in a certain order according to certain rules. In fact, this process is traversal, which corresponds to the iterator mode in the computer design mode. The iterator pattern provides a way to sequentially access various elements in an aggregate object without exposing the internal representation of that object.

Why iterators

Recall that there are many structures and ways to iterate in our javascript. Map and Set have been added to the existing JavaScript data structures that represent “collections”, mainly arrays and objects. Thus, there are four types of data sets, and there are different methods for traversing these four structures. For example, the server provides data to the front end, and the front end visualizations data and demonstrates the traversal of data using for. However, due to the change of business, the data structure returned by the back end is changed, and objects, sets and maps are returned, resulting in massive rewriting of the front-end traversal code. The purpose of iterators is to standardize iteration.

How do I deploy the iterator interface

ES6 introduces an implicit standardized interface for iterators. Many of Javascript’s built-in data structures, such as Array, Map, Set, String, TypedArray, function arguments objects, and NodeList objects, have Iterator interfaces. Iterator (which is short for Symbol(‘ symbol.iterator ‘)), The attribute name is Symbol and the type represents the unique and non-overrides of the attribute.) This is the iterator function, which returns an iterator object when executed.

While many of Javascript’s built-in data structures already implement this interface, there are also structures that don’t have iterator interfaces (such as objects). We need to write iterators, so we need to know how iterators work. The following code implements a simple iterator:

   // An iterator is a function, also called an iterator generator
function Iterator(o){
    let curIndex = 0;
    let next = (a)= > {
        return {
            value: o[curIndex],
            done: o.length == ++curIndex
        }
    }
    // Returns an iterator that has the next method
    return {
        next
    }
}
let arr = [1.2]
let oIt = Iterator(arr)
oIt.next();//{value:1,done:false}
oIt.next();//{value:2,done:false}
oIt.next();// {value: undefined, done: true}
oIt.next();// {value: undefined, done: true}
Copy the code

Call the iterator function and return an object, the iterator object, that has the next method on it. Each call to the next method returns information about the current member of the data structure. Specifically, it returns an object containing both the value and done attributes. Where, the value attribute is the value of the current member, and the done attribute is a Boolean value indicating whether the traversal is complete.

Next () iteration

In the next call above, note the following:

  • The iterator does not report done:true when it gets the last element of the array, so it calls next() again, past the value at the end of the array, to get done:true.
  • Normally, calling next on an iterator that has already iterated will continue to return {value: undefined, done: true} without an error.

Optional return() and throw()

An iterator object must have a next method as well as optional return and throw methods.

The return method is defined to send a signal to the iterator that no more values will be extracted from the consumer.

        Object.prototype[Symbol.iterator] = function () {
            let curIndex = 0;
            let next = () => {
                return {
                    value: this[curIndex],
                    done: this.length == curIndex++
                }
            }
            return {
                next,
                return() {
                    console.log('Execute return')
                    return{}}}}let obj = {
            0: 'a',
            1: 'b',
            2: 'c'} // automatic invocation -- encountered a condition that prematurely terminates iterator consumptionfor (let item of obj) {
            if (item == 'c') {
                break
            } else{console.log(item)}} // Automatic call -- throws an exceptionfor (let item of obj) {
            if (item == 'c') {
                throw new Error('Errow')}else{console.log(item)}} // Manually calledlet ot = obj[Symbol.iterator]()
        console.log(ot.return())
Copy the code

In the above code, the execution of the throw method can be called automatically or manually under certain circumstances. The throw method mainly reports an exception/error to an iterator and is used in conjunction with a generator.

Iterator classification

Iterators are divided into inner iterators and outer iterators.

  • Inner iterator: a function that defines the iteration rules internally and accepts the entire iteration process with only one call from the outside. For example, array.prototype. forEach and jquery. each are internal iterators.
  • External iterator: a function that returns an iterator, and the next element of the iteration must be explicitly called. With forEach traversal, all data can only be pulled and consumed at one time, while iterators can be used to control the behavior step by step, making the iteration process more flexible and controllable.

Iterator use

After implementing the iterator interface, how do you use it?

let arr = ['a'.'b'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a'.done: false }
iter.next() // { value: 'b'.done: false }
iter.next() // { value: undefined, done: true }
Copy the code

In addition to being used in isolation like the code above, the purpose of implementing this interface is to provide a unified access mechanism for all data structures. Implementing this interface makes it possible to call new apis in ES6 that are implemented by calling Iterator interfaces, such as for.. Of is a typical API for consuming iterators. Let’s look specifically at for.. The realization principle of OF:

letArr = [1, 2, 3];for(letnum of arr){ console.log(num); } the output is: 1,2,3Copy the code

Iterator.next () is called, and the value attribute of the iterator result object is put into the num variable. The items in the array are stored in the num variable until the done property in the iterator result object becomes true, and the loop ends. For-of loops completely remove the need to track collection indexes in for-loops and focus more on manipulating collection contents.

ES6 states that the default Iterator interface is deployed in the symbol. Iterator property of a data structure, or that a data structure can be considered “iterable” as long as it has the symbol. Iterator property. You can use the above API that calls Iterator functions by default, but how can you maximize interoperability if the data structure does not provide implementations of the interface (such as objects)? Then you can build your own iterators that meet this standard. Here is an example of adding an Iterator interface to an object:

        let obj = {
            0: 'a',
            1: 'b',
            2: 'c',
            length: 3,
            [Symbol.iterator]: function () {
                let curIndex = 0;
                let next = () => {
                    return {
                        value: this[curIndex],
                        done: this.length == curIndex++
                    }
                }
                return {
                    next
                }
            }
        }
        for (let item of obj) {
            console.log(item)
        }
Copy the code

Uncaught TypeError: obj is not iterable Uncaught TypeError: obj is not iterable

Besides the one shown above for.. In addition to the of loop that can consume iterators one by one, there are other ES6 constructs that can also consume iterators. For example, the spread operator:

        functionf(x, y, z) { console.log(x, y, z) } f(... [2, 3, 1))Copy the code

And structure assignment can also consume part or all of an iterator:

        letArr = [1, 2, 3, 4, 5] var it = arr[symbol.iterator]() var [x, y] = it console.log(x, y) Z] = it console.log(y, z) // print 3 [4,5]Copy the code

JavaScript produces iterators by default

To produce iterator objects, we can produce iterator objects by defining iterator functions or by calling iterator functions defined in JavaScript in built-in data structures. In addition, for arrays and the new data structures MAP and Set in ES6, these collections not only deploy iterator interfaces themselves, but also provide API methods to generate iterator objects. ES6 arrays, sets, and maps all deploy the following three methods, which return a traverser object when called.

  • Entries () returns an traverser object that iterates through an array of [key names, key values].
  • Keys () returns a traverser object that iterates over all the key names.
  • Values () returns a traverser object that iterates over all key values.

An iterator use example of an array

The iterator interface for arrays is used as follows:

letArr = [1, 2, 3, 4]let arrEntires = arr.entries()
arrEntires.next() //{value: [0, 1], done: false}
letArrkeys.next () //{value: 0,done: false}
let arrValues = arr.values()
arrValues.next() //{value: 1, done: false}
Copy the code

The code below shows the array’s for… The default iterator interface for the OF traversal is VALUES

for(letItem of [1,2,3]) {console.log(item)// [1,2,3]}Copy the code

An iterator use example of Set

Here is the iterator interface for Set:

let set= new Set ([1, 2, 3, 4])let setEntires = set.entries()// For sets, the key name is the same as the key value.setEntires.next() //{value: [1, 1], done: false}
let setKeys = set.keys()
 setKeys.next() //{value: 1, done: false}
let setValues = set.values()
setValues.next() //{value: 1, done: false}
Copy the code

The default iterator interface for Set [Symblo. Iterator] is values

for(letItem of new Set ([1, 2, 3, 4]) {the console. The log (item) / / [1, 2, 3, 4]}Copy the code

Map iterator example

The following is the iterator interface for Map:

letMap = new map ([[1, 2], [3, 4]])let mapEntires = map.entries()

mapEntires.next() //{value: [1, 2], done: false}
let mapKeys = map.keys() 
mapKeys.next() //{value: 1, done: false}
let mapValues = map.values()
mapValues.next() //{value: 2, done: false}
Copy the code

The default iterator interface for Map [symblo. iterator] is entries;

for(letThe item of the new Map ([[1, 2], [3, 4]])) {the console. The log (item) / / [1, 2] [3, 4]}Copy the code

Why don’t objects have built-in iterator interfaces

In the above, we mentioned that objects that do not have iterable default methods are non-iterable, represented by the fact that they do not have [symbol.iterator] properties. Even though objects are, for us, a way to store keys and values, not as good as maps, keys can only be strings, sometimes objects need to be iterated over, but why not give objects iterable by default?

And the reason for that is, when you iterate over an object, you have to think about whether it’s a property of the object itself or whether it’s an enumerable property of the object itself or whether it’s an enumerable property of the prototype or whether you want to iterate over an enumerable property of the prototype or whether you want to iterate over [symbol.iterator]. In view of the different opinions and the existing traversal methods, the standards group did not include [symbol.iterator].

A method to generate an iterator object

Above, we tried adding the symbol. iterator method to an object, which is the iterator generator for that object. Calling this function returns an iterator object for that object.

Is there any other way to generate iterators from the iterator protocol other than by adding the iterator generator function to the object above? Yes, it’s a special kind of function called a generator.

var it = {};
it[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; // Can be... Console. log([...it])// [1, 2, 3]let myIterator = it[Symbol.iterator]()
console.log(myIterator.next())//{value: 1, done: false}
console.log(myIterator.next())//{value: 2, done: false}
console.log(myIterator.next())//{ value: 3, done: false }
console.log(myIterator.next())//{ value: undefined, done: true }
Copy the code

In the above code, the generator function doesn’t have much code and just needs to use the keyword yeild to return the value of next() each time.

A generator is a special form of function. The declaration syntax of a generator function is:

function *bar(){
    // ...
}
Copy the code

* It can be preceded by or without Spaces. Generator functions are declared differently from normal functions, but they are executed the same way and can pass arguments. So what’s the main difference?

A function is a block of code that performs a specific task, so the function executes as if the block of code were executed. The function starts executing, and it’s not interrupted until it’s finished executing, and the block of code is completely executed. Functions did perform this way before generators were introduced in ES6, but it was noted that external iterators can control the iteration process over internal iterators, and when consumption is needed, the iterator object can be next. Similar to the iterative process, the execution process of a function can be controlled, and the function does not need to be executed all at once.

Execution of a generator function returns an iterator object that controls the generator function to execute its code. As a result, the execution of the function becomes controllable. You can also use the new yield keyword in the generator to indicate a pause point. In addition to controlling function execution, iterators can also pass information in both directions during each pause. The generator function returns a value during pause, and the iterator can pass a value inside the function when resuming execution by passing a parameter to the next method. Can be interpreted as multiple arguments, multiple return values.

Since this article focuses on iterators, we won’t cover more about generators in this article. Welcome to add and correct the introduction of iterators.