I. Syntax
Creation syntax: function* + yield
Usage syntax: next(returnValue).value/done
P.S. Iterator vs Generator: The thing defined by function* is called an iterator generator (generator for short), because calling it returns an iterator.
II. Purpose of Iterators
1. Function Throttling
Divide complex time-consuming tasks into small pieces using yield to do gradually, also called function currying. For more information, please check JS Learning Notes 11_Advanced Techniques
2. Generate Infinite Sequences
For example, Fibonacci sequence
3. Convenient Traversal
No need to manually maintain internal state
III. JavaScript Generator Examples
1. Basic Usage
function* fun(a) {
yield a + 1;
yield a * 2;
yield a * 2 + 1;
}
var iter = fun(3);
iter.next();
// => Object {value: 4, done: false}
iter.next();
// => Object {value: 6, done: false}
iter.next();
// => Object {value: 7, done: false}
function* fun(a) {
yield a=a + 1;
yield a=a * 2;
yield a=a * 2 + 1;
}
var iter = fun(3);
// => iter.next();
Object {value: 4, done: false}
iter.next();
// => Object {value: 8, done: false}
iter.next();
// => Object {value: 17, done: false}
iter.next();
// => Object {value: undefined, done: true}
When function execution encounters yield, it first returns the value after yield, then saves the function execution context (effect similar to saving a breakpoint). When next() is called next time, it restores the context and continues execution from the next statement after yield, exiting when encountering yield or return.
2. Advanced Usage
function* fib() {
var a = 1;
var b = 1;
while (true) {
var current = b;
b = a;
a = a + current;
var reset = yield current;
if (reset) {
a = 1;
b = 1;
}
}
}
var fibSeq = fib();
fibSeq.next();
// => Object {value: 1, done: false}
fibSeq.next();
// => Object {value: 1, done: false}
fibSeq.next();
// => Object {value: 2, done: false}
fibSeq.next();
// => Object {value: 3, done: false}
fibSeq.next();
// => Object {value: 5, done: false}
fibSeq.next(false);
// => Object {value: 8, done: false}
fibSeq.next(true);
// => Object {value: 1, done: false}
fibSeq.next(false);
// => Object {value: 1, done: false}
fibSeq.next(false);
// => Object {value: 2, done: false}
next() can accept parameters, this parameter will be passed back to the function as the return value of yield. This way you can control the internal state of the function, such as whether to reset above.
3. A Confusing Example
function* fun(a) {
a = yield a + 1;
a = yield a * 2;
yield a * 2 + 1;
}
var iter = fun(3);
iter.next(0);
// => Object {value: 4, done: false}
iter.next(0);
// => Object {value: 0, done: false}
iter.next(1);
// => Object {value: 3, done: false}
Similarly:
function* fun(a) {
a = yield a = a + 1;
a = yield a = a * 2;
yield a = a * 2 + 1;
}
var iter = fun(3);
iter.next(0);
// => Object {value: 4, done: false}
iter.next(0);
// => Object {value: 0, done: false}
iter.next(1);
// => Object {value: 3, done: false}
The internal execution mechanism is the same, just the writing style is more confusing.
4. Others
Additionally, iterators also have throw() and return() methods. FF implemented both, Chrome only implemented the former. For more information, please check MDN Iterators and generators
There's also syntax for using one iterator to generate another iterator, for example:
function* fun() {
yield 1;
yield 2;
}
var iter = fun();
var newIter = (for (i of iter) i * 2);
newIter.next();
// => Object { value: 2, done: false }
newIter.next();
// => Object { value: 4, done: false }
Note, MDN's example is incorrect, it must be for...of, and must be parentheses (the for...in and square brackets in the example are wrong).
FF supports this syntax, Chrome does not.
III. Related Syntax
1. for...of, for...in, forEach
for...of is used to traverse property values, for...in is used to traverse property names, forEach is a method on Array.prototype that can traverse both property names and property values.
Additionally, for...of can also traverse DOM NodeList and custom iterators (function* + yield), it's a new ES6 feature.
Examples as follows:
var arr = [3, 5, 7];
arr.foo = "hello";
for (var i in arr) {
console.log(i); // logs "0", "1", "2", "foo"
}
for (var i of arr) {
console.log(i); // logs "3", "5", "7"
}
let arr = [3, 5, 7];
arr.foo = "hello";
arr.forEach(function (element, index) {
console.log(element); // logs "3", "5", "7"
console.log(index); // logs "0", "1", "2"
});
2. yield*
Similar in function to yield, also used for traversal, but yield* is followed by an iterator object. Its purpose is to enter the following iterator, traverse it completely then return, for example:
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
With yield*, iterators can be nested, allowing "linear" traversal of hierarchical structures (of course,前提是 getting all lower-level structure iterator objects).
No comments yet. Be the first to share your thoughts.