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~