JavaScript: The Definitive Guide, Sixth Editio javaScript权威指南(第6版) pdf 文字版-文字版, javascript电子书, 和javascript 有关的电子书:

11.4.3 Generators

o = {x:1, y:2} // An object with two properties Object.prototype.z = 3; // Now all objects inherit z for(p in o) console.log(p); // Prints "x", "y", and "z" for(p in Iterator(o, true)) console.log(p); // Prints only "x" and "y"


11.4.3 Generators

Generators are a JavaScript 1.7 feature (borrowed from Python) that use a new yield keyword, which means that code that uses them must explicitly opt in to version 1.7, as described in §11.2 . The yieldkeyword is used in a function and functions something like return to return a value from the function. The difference between yield and return, however, is that a function that yields a value to its caller retains its internal state so that it is resumable. This resumability makes yield a perfect tool for writing iterators. Generators are a very powerful language feature, but they can be tricky to understand at first. We’ll begin with some definitions.

Any function that uses the yield keyword (even if the yield is unreachable) is a generator function. Generator functions return values with yield. They may use the return statement with no value to terminate before reaching the end of the function body, but they may not use return with a value. Except for their use of yield, and this restriction on the use of return, generator functions are pretty much indistinguishable from regular functions: they are declared with the function keyword, the typeof operator returns “function”, and they inherit from Function.prototype just as ordinary functions do. When invoked, however, a generator function behaves completely differently than a regular function: instead of executing the body of the generator function, the invocation instead returns a generator object.

A generator is an object that represents the current execution state of a generator function. It defines a next() method that resumes execution of the generator function and allows it to continue running until its next yield statement is encountered. When that happens, the value of the yield statement in the generator function becomes the return value of the next() method of the generator. If a generator function returns (by executing a return statement or reaching the end of its body), the next() method of the generator throws StopIteration.

11.4 Iteration | 277

The fact that generators have a next() method that can throw StopIteration should make it clear that they are iterator objects.1 In fact, they are iterable iterators, which means that they can be used with for/in loops. The following code demonstrates just how easy it is to write generator functions and iterate over the values they yield:

// Define a generator function for iterating over a range of integers

function range(min, max) {

for(let i = Math.ceil(min); i <= max; i++) yield i;

}

// Invoke the generator function to obtain a generator, then iterate it. for(let n in range(3,8)) console.log(n); // Prints numbers 3 through 8.

Generator functions need never return. In fact, a canonical example is the use of a generator to yield the Fibonacci numbers:

// A generator function that yields the Fibonacci sequence

function fibonacci() {

let x = 0, y = 1;

while(true) {

yield y;

[x,y] = [y,x+y];

}

}

// Invoke the generator function to obtain a generator.

f = fibonacci();

// Use the generator as an iterator, printing the first 10 Fibonacci numbers.

for(let i = 0; i < 10; i++) console.log(f.next());

Notice that the fibonacci() generator function never returns. For this reason, the generator it returns will never throw StopIteration. Rather than using it as an iterable object in a for/in loop and looping forever, we use it as an iterator and explicitly call its next() method ten times. After the code above runs, the generator f still retains the execution state of the generator function. If we won’t be using it anymore, we can release that state by calling the close() method of f:

f.close();

When you call the close method of a generator, the associated generator function terminates as if there was a return statement at the location where its execution was suspended. If this location is inside one or more tryblocks, any finally clauses are run before close() returns. close() never has a return value, but if a finally block raises an exception it will propagate from the call to close().

Generators are often useful for sequential processing of data—elements of a list, lines of text, tokens from a lexer, and so on. Generators can be chained in a way that is analogous to a Unix-style pipeline of shell commands. What is interesting about this

1. Generators are sometimes called “generator iterators” to clearly distinguish them from the generator functions by which they are created. In this chapter, we’ll use the term “generator” to mean “generator iterator.” In other sources, you may find the word “generator” used to refer to both generator functions and generator iterators.

approach is that it is lazy: values are “pulled” from a generator (or pipeline of generators) as needed, rather than being processed in multiple passes. Example 11-1 demonstrates.

Example 11-1. A pipeline of generators

// A generator to yield the lines of the string s one at a time. // Note that we don't use s.split(), because that would process the entire // string at once, allocating an array, and we want to be lazy instead. function eachline(s) {

let p;

while((p = s.indexOf('\n')) != -1) { yield s.substring(0,p); s = s.substring(p+1);

} if (s.length > 0) yield s; }

// A generator function that yields f(x) for each element x of the iterable i function map(i, f) { for(let x in i) yield f(x); }

// A generator function that yields the elements of i for which f(x) is true function select(i, f) { for(let x in i) { if (f(x)) yield x; } }

// Start with a string of text to process let text = " #comment \n \n hello \nworld\n quit \n unreached \n";

// Now build up a pipeline of generators to process it. // First, break the text into lines let lines = eachline(text); // Next, trim whitespace from the start and end of each line let trimmed = map(lines, function(line) { return line.trim(); }); // Finally, ignore blank lines and comments let nonblank = select(trimmed, function(line) {

return line.length > 0 && line[0] != "#" });

// Now pull trimmed and filtered lines from the pipeline and process them, // stopping when we see the line "quit". for (let line in nonblank) {

if (line === "quit") break; console.log(line); }

Typically generators are initialized when they are created: the values passed to the generator function are the only input that the generator receives. It is possible, however, to provide additional input to a running generator. Every generator has a send() method, which works to restart the generator like the next() method does. The difference

11.4 Iteration | 279

is that you can pass a value to send(), and that value becomes the value of the yield expression. (In most generator functions that do not accept additional input, the yield keyword looks like a statement. In fact, however, yield is an expression and has a value.) In addition to next() and send(), another way to restart a generator is with throw(). If you call this method, the yield expression raises the argument to throw() as an exception. The following code demonstrates:

// A generator function that counts from an initial value. // Use send() on the generator to specify an increment. // Use throw("reset") on the generator to reset to the initial value. // This is for example only; this use of throw() is bad style. function counter(initial) {

let nextValue = initial; // Start with the initial value while(true) {

try { let increment = yield nextValue; // Yield a value and get increment if (increment) // If we were sent an increment...

nextValue += increment; // ...then use it.

else nextValue++; // Otherwise increment by 1 } catch (e) { // We get here if someone calls

if (e==="reset") // throw() on the generator nextValue = initial; else throw e; } } }

let c = counter(10); // Create the generator at 10 console.log(c.next()); // Prints 10 console.log(c.send(2)); // Prints 12 console.log(c.throw("reset")); // Prints 10

欢迎转载,转载请注明来自一手册:http://yishouce.com/book/1/31401.html
友情链接It题库(ittiku.com)| 版权归yishouce.com所有| 友链等可联系 admin#yishouce.com|粤ICP备16001685号-1