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.