JS 迭代器(ITERATION)和生成器(GENERATOR)

富波光
2023-12-01

Iteration

First question, what is the Iteration?

  In the dictionary, ‘iteration’ means ‘the repetition of a process or utterance’.
And in a program, it equals that repeat the code segment in order.

  Let me give you an example:

for(let i = 0; i <= 10; i++){
	console.log(i);
}

this is the easiest iteration, or one of the easiest.

  So if you do not have any idea about how that code work, I suggest you to learn more basic knowledge about JS.

Iteration and spread operators in ES6

  ECMAScript 6 introduced iterators and the spread operator, which are especially useful in the context of collection reference types.
  And many built-in types implement a default iterator:

  • Array
  • All typed arrays
  • String
  • Map
  • Set
  • The arguments object
  • Some Dom collection types like NodeList

  In that case, there is no doubt that all support ordered iteration and can be passed to a for…of loop:
  eg.

let arr = ['a', 'b', 'c'];
for (let str of arr){
	console.log(str);
}

  Dose it looks so familiar?Yes, you may have use that loop for may times.

  But do you know how it worked?

The Iterator Protocol

  The Iterator API uses a next() method to advance through the iterable.
  Each successive time next() is invoked, it will return an IteratorResult object containing the next value in the iterator.

  The next() method returns an object with two projects:done(boolean) and value(next value/ undefined)

  If done is true, that state is termed “exhaustion.”
  eg in document:

// Iterable object
let arr = ['foo', 'bar'];

// Iterator factory
console.log(arr[Symbol.iterator]); // f values() { [native code] }

// Iterator
let iter = arr[Symbol.iterator]();
console.log(iter); // ArrayIterator {}

// Performing iteration
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }

  Once the iterator reaches the done:true statr, invoking next() is idempotent

let arr = ['foo'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: true, value: undefined } 
console.log(iter.next()); // { done: true, value: undefined }

  Different instances are not aware of each other and will independently traverse the iterable

let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator](); 
let iter2 = arr[Symbol.iterator]();

console.log(iter1.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'foo' } 
console.log(iter2.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // { done: false, value: 'bar' }

Customize Iterator

   And there is a customize iterator
eg:


class Counter {
 constructor(limit) {
  this.limit = limit;
 }

 [Symbol.iterator]() {
  let count = 1,
    limit = this.limit;
  return {
   next() {
    if (count <= limit) {
     return { done: false, value: count++ };
    } else {
     return { done: true, value: undefined };
    }
   }
  };
 }
}

let counter = new Counter(3);

for (let i of counter) { console.log(i); }
// 1
// 2
// 3
for (let i of counter) { console.log(i); }
// 1
// 2
// 3

Early termination of iterators

  A for…of loop exits early via break, continue, return, or throw.

class Counter {
 constructor(limit) {
  this.limit = limit;
 }

 [Symbol.iterator]() {
  let count = 1,
    limit = this.limit;
  return {
   next() {
    if (count <= limit) {
     return { done: false, value: count++ };
    } else {
     return { done: true };
    }
   },
   return() {
    console.log('Exiting early');
    return { done: true };
   }
  };
 }
}


let counter1 = new Counter(5);

for (let i of counter1) {
 if (i > 2) {
  break; 
 }
 console.log(i);
}
// 1
// 2
// Exiting early


let counter2 = new Counter(5);

try {
 for (let i of counter2 {
  if (i > 2) {
   throw 'err'; 
  }
  console.log(i);
 }
} catch(e) {}
// 1
// 2
// Exiting early


let counter3 = new Counter(5);

let [a, b] = counter3;
// Exiting early

  If an iterator is not closed, then you are able to pick up iteration where you left off. For example, Array Iterators are not closable:


let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();

for (let i of iter) {
 console.log(i);
 if (i > 2) {
  break
 }
}
// 1
// 2
// 3

for (let i of iter) {
 console.log(i);
}
// 4
// 5

Generator

  Generators are a delightfully flexible construct introduced in the ECMAScript 6 specification that offers the ability to pause and resume code execution inside a single function block.

Basic

  Generators take the form of a function, and the generator designation is performed with an asterisk

// Generator function declaration
function* generatorFn() {}

// Generator function expression
let generatorFn = function* () {}

// Object literal method generator function
let foo = {
 * generatorFn() {}
}

// Class instance method generator function
class Foo {
 * generatorFn() {}
}

// Class static method generator function
class Bar {
 static * generatorFn() {}
}

Interrup ting execution with “yield”

  The yield keyword allows generators to stop and start execution, and it is what makes generators truly useful. Generator functions will proceed with normal execution until they encounter a yield keyword.

  eg

function* generatorFn() {
 yield 'foo';
 yield 'bar';
 return 'baz';
}

let generatorObject = generatorFn();

console.log(generatorObject.next()); // { done: false, value: 'foo' } 
console.log(generatorObject.next()); // { done: false, value: 'bar' } 
console.log(generatorObject.next()); // { done: true, value: 'baz' } 

  The yield keyword can be simultaneously used as both an input and an output, as is shown in the following example:

function* generatorFn() {
 return yield 'foo';
}

let generatorObject = generatorFn();

console.log(generatorObject.next());      // { done: false, value: 'foo' }
console.log(generatorObject.next('bar')); // { done: true, value: 'bar' } 

  The yield keyword is not limited to a one-time use. An infinite counting generator function can be defined as follows:

function* generatorFn() {
 for (let i = 0;;++i) {
  yield i;
 }
}

let generatorObject = generatorFn();

console.log(generatorObject.next().value); // 0
console.log(generatorObject.next().value); // 1
console.log(generatorObject.next().value); // 2
console.log(generatorObject.next().value); // 3
console.log(generatorObject.next().value); // 4
console.log(generatorObject.next().value); // 5 
...

Using a generator as the default iterator

  Because generator objects implement the Iterable interface, and because both generator functions and the default iterator are invoked to produce an iterator, generators are exceptionally well suited to be used as default iterators.

eg

class Foo {
 constructor() {
  this.values = [1, 2, 3];
 }
 * [Symbol.iterator]() {
  yield* this.values;
 }
}

const f = new Foo();

for (const x of f) {
 console.log(x);
}
// 1
// 2
// 3

  NOTE:If the generator object has not yet begun execution, calling throw() cannot be caught inside the function because the error is thrown from outside the function block.

  If this blog helps you, can you give a like ! ! Thank you ! !

 类似资料: