You must know the iterator and generator

generator is a special iterator, which can replace the implementation of iterator to make the code more concise

What is an iterator

An iterator is called an iterator. It is an object used to help traverse a data structure. This object needs to comply with the iterator protocol.

The iterator protocol requires the implementation of the next method, which has the following requirements

  • 0 or 1 function arguments
  • The return value needs to include two attributes, done and value.
    When the traversal is complete, done is true and value is undefined.

Implementation principle of iterator

  • Create a pointer object to the starting position of the current data structure
  • When the next method of the object is called for the first time, the pointer automatically points to the first member of the data structure
  • Next, the next method is called continuously, and the pointer moves back until it points to the last member
  • Each call to the next method returns an object containing the value and done attributes

The following objects implement the iterator

const names = ["kiki", "alice", "macus"];
let index = 0;
const namesIterator = {
  next: () => {
    if (index == names.length) {
      return { value: undefined, done: true };
    } else {
      return { value: names[index++], done: false };
    }
  },
};

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

When the next method is called for the fourth time, the data has been iterated, so the iterator returns done as true

Iteratable object

Iteratable objects are different from iterator objects.

  • The iterator object needs to conform to the iterator protocol and return the next method.
  • Iteratable objects need to implement the iteratable protocol, i.e. @ @ iterator method, through symbol. In the code Iterator implementation, so that when the data is for When of is traversed, the @ @ iterator method is called.

We know that the data type of object can not be used through for Of traverses it

But if we implement the @ @ iterator method on it, it becomes an iteratable object

const obj = {
  name: "alice",
  age: 20,
  hobby: "singing",
  [Symbol.iterator]: function () {
    let index = 0;
    const keys = Object.keys(this);
    return {
      next: () => {
        if (index == keys.length) {
          return { value: undefined, done: true };
        } else {
          const key = keys[index];
          index++;
          return { value: this[key], done: false };
        }
      },
    };
  },
};
for (let item of obj) {
  console.log(item);
}
const iterator = obj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

Iterator objects are included in the implementation of iteratable objects.

Native iteratable object

Simply put, you can use for Of traverses iteratable objects. Native iteratable objects include arrays, strings, arguments, set s, and map s

const arr = ["kiki", "alice", "macus"];
for (let item of arr) {
  console.log("array", item);
}

const str = "hello";
for (let s of str) {
  console.log("character string", s);
}

function foo() {
  for (let arg of arguments) {
    console.log("arguments", arg);
  }
}
foo(1, 2, 3, 4);

const mapEntries = [
  ["name", "alice"],
  ["age", "20"],
];
const map = new Map(mapEntries);
for (let m of map) {
  console.log("map", m);
}

const set = new Set(arr);
for (let s of set) {
  console.log("set", s);
}

These are all native iteratable objects

Purpose of iteratable objects

Iteratable objects serve the following purposes

  • for...of traversal
  • Expand syntax
  • Destructuring assignment
  • Create other types of objects, such as array and set
  • Promise.all can also execute iteratable objects

Object can be assigned using expansion syntax and deconstruction, but it is not an iteratable object, but a separate property implemented in es9

const iteratorObj = {
  names: ["kiki", "alice", "macus"],
  [Symbol.iterator]: function () {
    let index = 0;
    return {
      next: () => {
        if (index == this.names.length) {
          return { value: undefined, done: true };
        } else {
          return { value: this.names[index++], done: false };
        }
      },
    };
  },
};
for (let item of iteratorObj) {
  console.log('for..of Traversing iteratable objects:',item);
}

const newArr = [...iteratorObj];
console.log('Expand syntax:',newArr);

const [name1, name2, name3] = iteratorObj;
console.log('Deconstruction assignment:',name1, name2, name3);

const set = new Set(iteratorObj);
const arr = Array.from(iteratorObj);
console.log('set: ',set);
console.log('array: ',arr)

Promise.all(iteratorObj).then((value) => {
  console.log("promise.all: ", value);
});

The above methods are to obtain the value value in the next method

Iteration of custom classes

To make a class an iteratable object, add a symbol to the class method And implement the iterator method

class Student {
  constructor(name, age, hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }
  push(hobby) {
    this.hobbies.push(hobby);
  }
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index === this.hobbies.length) {
          return { done: true, value: undefined }
        } else {
          return { value: this.hobbies[index++], done: false };
        }
      },
    };
  }
}

const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
  console.log(item)
}

You can use for The of method traverses the hobbies property in the class

What is a generator

The generator is called a generator and can be used to control when functions execute and pause.

Generator functions are also functions, but there are the following differences between generator functions and ordinary functions

  • One needs to be added between generator functions*
  • Execution requires a variable to receive, and each time the next() method is used, a piece of code is executed
  • Pause the function through the yield keyword. Yield can pass both parameters and return values
  • The return value is a generator, which is a special iterator

The above code implements the generator function

function* foo(){
  console.log('Start execution')
  yield
  console.log('world')
  yield
  console.log('End execution')
}
const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

When calling the next method, the return value is consistent with the iterator. It is an object containing value and done. At this time, value is undefine because the return value is not added after yield

yield pass parameter and return value

Parameters can be passed to the generator function through the next method, and data can be returned through yield

function* foo(value1){
  console.log('Start execution')
  const result1 = yield value1
  const result2 = yield result1
  const result3 = yield result2
  console.log('End execution')
}

const generator = foo('hello')
console.log(generator.next('world'))
console.log(generator.next('merry'))
console.log(generator.next('christmas'))
console.log(generator.next('done'))

You can see that the value obtained by the first next is passed through the generator function instead of the parameter when the first next method is executed, so the value value is "hello" instead of "world"

Other methods of generator

  • The throw method is used to throw an exception (which needs to be caught in the generator function)
  • The return method is used to interrupt the execution of the generator function
function* createGenerator() {
  console.log("Start execution");
  try {
    yield "hello";
    console.log("hello");
  } catch (error) {
    yield error;
  }
  yield "world";
  console.log("End execution");
}

const generator = createGenerator();
console.log(generator.next());
console.log(generator.throw("throw"));
console.log(generator.return("return"));

After using the return method, done becomes true and value becomes the value passed in the return function

Generator override iterator

Generator is a special iterator, which can make some substitutions in some scenes to make the code more concise

// The iterator implements the next method
function createArrayIterator(arr) {
  let index = 0;
  return {
    next: function () {
      if (index == arr.length) {
        return { value: undefined, done: true };
      } else {
        return { value: arr[index++], done: false };
      }
    },
  };
}

// generator traverses the pause function
function* createArrayGenerator(arr) {
  for(let item of arr){
    yield item
  }
}

// yiled grammar sugar
function* createArraYield(arr) {
  yield* arr
}

const arr = ['alice', 'kiki', 'macus']
const iterator = createArrayIterator(arr)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

const generator = createArrayGenerator(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

const yiledGen = createArraYield(arr)
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())

The functions realized by the above three methods are consistent

Class

Class instead of iterator

class Student {
  constructor(name, age, hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }
  push(hobby) {
    this.hobbies.push(hobby);
  }
  *[Symbol.iterator]() {
    yield* this.hobbies
  }
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
  console.log(item)
}

The above is the usage and connection of iterator and generator. There are still many places for developers to master about js advanced. You can see other blog posts I wrote and keep updating~

Keywords: Javascript Front-end ECMAScript generator iterator

Added by scott56hannah on Tue, 28 Dec 2021 01:35:14 +0200