Iterator (traverser)
What is an iterator?
First, an iterator is an object (which is special) that iterates through an array-like data structure
Second, there are two methods in this object
- Next () // Iterate over the members of the data structure
- Return () // Terminates traversal
Why iterators?
Can improve development efficiency! Developers don’t need to know the structure of the data structure to get the data in a certain order. Isn’t it very high class?
So how do you generate an iterator?
To know how to generate an iterator, we need to know what is an iterable protocol
Implementing the Iteratable interface (an iterable protocol) requires a combination of two capabilities: the ability to self-identify to support iteration and the ability to create objects that implement the Iterator interface. In ES, this means that an attribute must be exposed as the “default iterator “, and that attribute must use the special symbol.iterator as the key. The default iterator property must refer to an iterator factory function, which must return a new iterator.
In plain English, when creating an iterable, the iterable must define an iterator factory function so that it can use its iterators to iterate over its own data.
Many built-in types are iterable, which means they have their own built-in iterators! , such as
- string
- An array of
- mapping
- A collection of
- The arguments object
- DOM collection type such as NodeList
Play with the iterator now!
let array=["abc"."hello"."hy"."world"];
let arrayIterator=array[Symbol.iterator]();
/* Call the array iterator factory function to produce an array iterator */
console.log(arrayIterator.next());//{ value: 'abc', done: false }
console.log(arrayIterator.next());//{ value: 'hello', done: false }
console.log(arrayIterator.next());//{ value: 'hy', done: false }
console.log(arrayIterator.next());//{ value: 'world', done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
Copy the code
Isn’t it amazing? , the above code can continue to optimize!
let array=["abc"."hello"."hy"."world"];
let arrayIterator=array[Symbol.iterator]();
for(value of array){
console.log(value);
}
/*
abc
hello
hy
world
*/
console.log("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --")
for(value of arrayIterator){
console.log(value);
}
/* The iterator of the built-in type is itself an iterable, so it can also be for.... ABC hello hy world */
Copy the code
Why is it possible to write that up here
Since there are several native language features that implement the Iterator interface, the Iterator interface can consume Iterable as long as it is implemented. Of is essentially an iterator, so it can consume arrays (iterables), not just for… Of and the following native language features implement the Iterator interface
- An array of deconstruction
- Extension operator
- Array.from()
- Create a collection
- Create a mapping
- Promise.all() Is an iterable composed of acceptance contracts
- Promise.race() Is an iterable composed of acceptance contracts
- Yield * operator, used in generators
Terminate the iterator early
Using an iterator object return () method, which can stop the iterator traverses, but should pay attention to is like an array of built-in objects, even if you execute the return () method, it is not going to close the iterator, and it will be kept before traversal of the scene, such as continue to bloom for next time, will then traverse the last place, continue to traverse.
So here’s the code
let array=[1.2.3.4.5];
let arrayIterator=array[Symbol.iterator]();
arrayIterator.return=function(){
console.log("Stop traversal");
return {done:true};
}
for(value of arrayIterator){
if(value >2) {break;
}
console.log(value);
}
/* 1 2 stop traversal */
console.log(arrayIterator.next());//{ value: 4, done: false }
console.log(arrayIterator.next());//{ value: 5, done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
Copy the code
Define a custom iterator
After looking at the built-in iterators above, do you want to implement an iterator for yourself ^^
Now let’s talk about what it takes to implement a custom iterator!
1 variable is defined in the iterator factory function
- Index: The position to hold the iterator pointer so that the next iteration will be followed by the next iteration (using closures to save the traversal scene)
let obj={
data: [0.1.3],
[Symbol.iterator](){
let index=0;// Save the position of pointer
const self=this;
return {
next(){
if(index<self.data.length)
{
return {value:self.data[index++],done:false};
}
else
return {value:"undefined".done:true}
}
}
}
}
let objIterator =obj[Symbol.iterator]();
console.log(objIterator.next());//{ value: 0, done: false }
console.log(objIterator.next());//{ value: 1, done: false }
console.log(objIterator.next());//{ value: 3, done: false }
console.log(objIterator.next());//{ value: 'undefined', done: true }
Copy the code
Look at the picture is not a lot clearer ^^
The generator
The Generator function is an asynchronous programming solution provided by ES6. It is either a state machine or an iterator Generator function.
Calling a Generator function returns an iterator that calls the next() function to iterate over the state in the Generator function.
Each yield keyword represents a state of the Generator function. As soon as the iterator traverses the yield keyword, the Generator function interrupts execution. When will it execute again? When the iterator calls the next() method again, the generator function starts executing again.
Let’s declare a simple Generator function.
function * createGenerator(){
console.log("start");
yield 1;
yield 2;
yield 3;
return "ending";
}
let Generator=createGenerator();// An iterator is generated. The generator function is called, but the code inside it is not executed. Only the next() method of the iterator can drive the generator function to execute the internal code.
console.log(Generator.next());
/* The Generator function starts executing until the first yield expression is encountered. The next method returns an object (IteratorResult) with the value of the current yield expression value 1 and the done value false, indicating that the traversal has not finished. So the "console.log("start")" in front of the "yield 1" statement is executed. * /
console.log(Generator.next());//{done:false,value:2}
console.log(Generator.next());//{done:false,value:3}
console.log(Generator.next());//{done:true,value:"ending"}
console.log(Generator.next());//{done:true,value:undefined}
console.log(Generator.next());//{done:tre,value:undefined}
Copy the code
Generator objects can be used as iterables
Without further ado, let’s get right to the code
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
}
let generatorIterator=generator();// The generator function returns an iterator (generator object)
let generatorIteratorIterator=generatorIterator[Symbol.iterator]();
/* The iterator returned by a generator function is essentially an iterable because it has a default iterator, so we can use for... The of structure consumes the generator object */
console.log(generatorIteratorIterator)
for(item of generatorIterator){
console.log(item);
}
/* 1, 2, 3, 4 */
// This is equivalent to the following code
console.log(generatorIterator.next().value);/ / 1
console.log(generatorIterator.next().value);/ / 2
console.log(generatorIterator.next().value);/ / 3
console.log(generatorIterator.next().value);/ / 4
Copy the code
Use yield to implement input and output
Ruan yifeng god from the copy of an example, I feel particularly good to understand
To clarify, the yield expression always returns undefined unless the next() method injects an argument to the previous yield expression. (This may be a little confusing, but I’ll do the figure above ^^ later.)
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()); // Object{value:6, done:false}
console.log(a.next()); // Object{value:NaN, done:false}
console.log(a.next()); // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
Copy the code
- Declares a variable, a, that receives an iterator returned by generator foo.
- Execute the first a.ext (), and the first a.ext () will output an IteratorResult object containing both value(yield expression value) and done(traversal completed), especially !!!! Assignment to y has been interrupted!!
- Execute the second A.next (), because the second A.next () method does not inject arguments into the generator function, so the last yield expression returns undipay, and the result is Y =2*undefined, which is why the output value of the second A.next () is NaN, The assignment to variable Z is then interrupted.
- Execute the third A.next (), because the third A.next () method does not inject arguments into the generator function, so the last yield expression returns undipay, and the result is z=undefined, which is why the output of the third A.next () is NaN
Everyone to simulate it!
Produces an iterable
You can use an asterisk to enhance the behavior of yield so that it iterates over an iterable, directly to the code
function *Generator(){
yield* [1.2.3]}for(item of Generator()){
console.log(item);
}
/ / equivalent
function *Generator(){
yield 1;
yield 2;
yield 3;
}
for(item of Generator()){
console.log(item)
}
Copy the code
Generator as default iterator
let obj={
data: [1.2.3],
[Symbol.iterator](){
let index=0;
let self=this;
return {
next(){
if(index<self.data.length){
return{
value:self.data[index++],
done:false}}else
return{
value:undefined.done:true
}
}
}
}
}
let obj1= {
data: [1.2.3],
[Symbol.iterator]:function *Generator(){
yield *this.data; }}for(item of obj){
console.log(item);
}
/* 1, 2, 3 */
for(item of obj1){
console.log(item);
}
/* 1, 2, 3 */
Copy the code
After reading the above code, it’s convenient to have generators as the default iterator!
Premature termination generator
The return method
The generator’s return() method can be used to prematurely terminate the generator. After the generator is shut down, subsequent calls to the next() method will display the done:true state, and any return values provided will not be stored or propagated.
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}
let Iterator=generator();
console.log(Iterator.next());//{ value: 1, done: false }
console.log(Iterator.next());//{ value: 2, done: false }
console.log(Iterator.return("Generator off"));//{value: 'generator off ', done: true}
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
Copy the code
Throw method
The throw() method injects a supplied error into the generator object when paused. If the error is not handled, the generator closes and goes directly to the code.
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}
let Iterator=generator();
try {
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.throw("Generator off"));
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
}catch(e){
console.log(e);
}
/* {value: 1, done: false} {value: 2, done: false} Generator closes */------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------function *generator(){
try{
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}catch(e){
console.log(e); }}let Iterator=generator();
try {
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.throw("Generator off"));
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
}catch(e){
console.log(e);
}
/* {value: 1, done: false} {value: 2, done: false} {value: undefined, done: true} {value: undefined, done: true} {value: undefined, done: false} true } { value: undefined, done: true } { value: undefined, done: true } { value: undefined, done: true } */
Copy the code
That’s it for generators. Let’s talk about some of their applications
Application scenarios
Coroutines scenario
What is a coroutine?
I will not say more here, you can refer to the article of Liao Xuefeng teacher, directly on the code
function cookTask(wash,cook){
let washIterator=wash();
let cookIterator=cook();
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
}
function *wash(){
yield "I washed the cabbage.";
yield "I washed the pork.";
yield "I washed the chicken wings.";
}
function *cook(){
yield 'I've cooked the cabbage.';
yield 'I cooked the pork.';
yield 'I cooked the chicken wings.';
}
cookTask(wash,cook);
/* I washed the cabbage I cooked the cabbage I washed the pork I cooked the pork I washed the chicken wings I cooked the chicken wings */
Copy the code
Here we simulate the scene of cooking, where there is a robot chef. First of all, the robot will perform the function of washing dishes. When the function of washing dishes is finished, the robot will transfer the control to the function of cooking dishes.
Asynchronous operation synchronization
This is an example of Ruan Yifeng
At the heart of synchronizing asynchronous Ajax operations is the yield keyword in the Generator function, which interrupts execution!
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
Copy the code
Let me give you some ideas
When the iterator IT calls the next() method for the first time, starting the generator, Ajax will initiate the request. When the Ajax is done, it will initiate the next() method again, using the callback function, assigning the received data to the last yield expression, and the variable result will receive the request from Ajax. Finally, the requested data is formatted and output.
Isn’t it amazing to use Generator functions to synchronize asynchronous tasks
At the end
This is the end of the article, if there is anything wrong with the above content, I hope the big guys can point out ^^