Basic concept

ES6 represents the data structure of the collection has the following four types, and can use them in combination to define their own data structure.

  • Array (Array)
  • Object (Object)
  • Map
  • Set

Iterator is a mechanism. It is also an interface that provides a unified access mechanism for a variety of different data structures. Any data can be iterated by deploying the Iterator interface.

Iterator does three things

  • It provides a unified and simple access interface for all kinds of data structures
  • Allows the members of a data structure to be arranged in some order
  • ES6 creates for… Of circulation

The details of the Iterator’s iteration are

1Create a pointer object to the starting position of the current data structure2The first call to the next method of a pointer object points to the first member of a data structure3The next call to the next method points to the second member of the data structure4, keep calling the next method until it points to the end of the data structureCopy the code

The following is a simulated example of next

var it = makeIterator(['a'.'b'])

it.next()	// {value: 'a', done: false}
it.next()	// {value: 'b', done: false}
it.next()	// {value: undeifned, done: true}

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

The default Iterator interface

A data structure is said to be Iterable whenever the Iterator interface is deployed.

The Iterator interface is deployed in the Symbol. Iterator property of the data structure.

eg1:

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

If we want to give objects the ability to use for… Of, the native obj is not iterable, we can add it to it using the iterator, and it can use the for of method.

var obj = {a: 1.b: 2.c: 3}

for (var item of obj) {
    console.log(item)
}
// TypeError: obj is not iterable
-------------------------------------

var obj = {
    a: 1.b: 2[Symbol.iterator]: function() {
        var iterator = {next: next}
        var current = 0    

        function next() {
            if (current < 3) {
                return {done: false.value: current++}
            } else {
                return {done: true}}}return iterator
    }
}

for (var item of obj) {
    console.log(item)
}
/ / 0 1 2
Copy the code

The data structures with the native Iterator interface are as follows

- Array
- Map
- String- TypedArray - functionsargumentsObject - NodeList ObjectCopy the code

Here is an array of symbol. iterator

let arr = [1, 2, 3]

let iter = arr[Symbol.iterator]()

console.log(JSON.stringify(iter.next()))
// {"value":1,"done":false}
console.log(JSON.stringify(iter.next()))
// {"value":2,"done":false}
console.log(JSON.stringify(iter.next()))
// {"value":3,"done":false}
console.log(JSON.stringify(iter.next()))
// {"done":true}
Copy the code

For natively deployed Iterator interface data structures, do not write their own traverser generator functions, for… The of loop will automatically iterate over them. In addition, the Iterator interfaces for other data structures (mainly objects) need to be deployed on the symbol. Iterator property to be used for… The of loop traverses.

// Class has symbol.iterator
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
Copy the code

The above code is a class that deploys the Iterator interface, so the instantiated object of the new class has the Symbol. Iterator property.

The following is an example of a pointer structure implemented through a traverser

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 }
        } else {
            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
Copy the code

Here is an example of adding an Iterator interface to an object

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
          };
        } else {
          return { value: undefined.done: true };
        }
      }
    }
  
  }
}

for (var a of obj) {
    console.log(a)
}
// hello
// world
Copy the code

An easy way to deploy the Iterator interface for array-like objects (with numeric keys and length attributes) is to use the symbol. Iterator method to refer directly 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')]
Copy the code

The NodeList object is an array-like object that has its own traversal interface and can be traversed directly. The code above changes its own traversal interface to the Symbol. Iterator property of the array without seeing any effect.

The following example is an example of calling the symbol. iterator method of an array on an array-like object

let iterator = {
	0: 'a'.1: 'b'.2: 'c'.length: 3[Symbol.iterator]: Array.prototype[Symbol.iterator]
}

for (let a of iterator) {
    console.log(a)
}
// a b c
Copy the code

Note that the array traverser is used for this object because it is an array-like object, and the keys are numbers. Array traversers are useless if deployed for other ordinary objects.

The context in which the Iterator interface is called

(1) Destruct assignment, array and Set destruct assignment, will call Symbol. Iterator method by default

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

let [x, y] = set
// x => a
// y => b

let [first, ...rest] = set
// first => a
// rest => ['a', 'b']
Copy the code

(2) Extension operator (…) The Iterator interface is also called

let str = 'hello'
[...str]	// ['h', 'e', 'l', 'l', 'o']
Copy the code

(3) yield* yield* is followed by a traversable deconstruction, which invokes the iterator

Iterator interface for strings

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

var it = someString[Symbol.iterator]()

it.next()
// {value: "h", done: false}
it.next()
// {value: "i, done: false}
it.next()
// {value: undefined, done: true}
Copy the code

In the code above, we call symbol. iterator to return an iterator object on which we can call the next method to iterate over the string.

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

var str = new String("hi");

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

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

console.log([...str])
console.log(str)
Copy the code

In the code above, the symbol. iterator method of the string STR has been modified

Iterator interface and Generator functions

The simplest implementation of the symbol. iterator method can be done using generators, which we’ll look at in detail in the next chapter.

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

/ / or
let obj = {
	* [Symbol.iterator] () {
    	yield 'hello';
        yield 'world'; }}for (let x of obj) {
	console.log(x)
}
// 'hello'	
// 'world'
Copy the code

Iterate over the object’s return and throw

An iterator object can have return and throw methods as well as next methods. If you write your own traverser object generator, the next method must be deployed, and the return and throw deployment is optional.

The return method is used for… The of loop exits prematurely, usually because of an error or a break statement, and the return method is called. The return method can be deployed if an object needs to clean up or release resources before completing traversal.

function readLinesSync (file) {
    return{[Symbol.iterator]() {
            return {
                next() {
                    return {done: false}},return() {
                    file.close()
                    return {done: true}
                }
            }
        }
    }
}
Copy the code

Both of the following situations trigger the return method

/ / a
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

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

In the code above, after case 1 outputs the first line of the file, the return method is executed to close the file. In case two, an error is thrown after the file is closed by the return method.

The throw method is used primarily with Generator functions.