C++,python,热爱算法和机器学习
全部博文(1214)
分类: JavaScript
2018-07-20 11:06:23
"Generator Iterator". Quite a mouthful, huh?
Iterators are a special kind of behavior, a design pattern actually, where we step through an ordered set of values one at a time by calling next(). Imagine for example using an iterator on an array that has five values in it: [1,2,3,4,5]. The first next() call would return 1, the second next() call would return 2, and so on. After all values had been returned, next() would return null or false or otherwise signal to you that you've iterated over all the values in the data container.
The way we control generator functions from the outside is to construct and interact with a generator iterator. That sounds a lot more complicated than it really is. Consider this silly example:
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; }
To step through the values of that *foo() generator function, we need an iterator to be constructed. How do we do that? Easy!
var it = foo();
Oh! So, calling the generator function in the normal way doesn't actually execute any of its contents.
That's a little strange to wrap your head around. You also may be tempted to wonder, why isn't it var it = new foo(). Shrugs. The whys behind the syntax are complicated and beyond our scope of discussion here.
So now, to start iterating on our generator function, we just do:
var message = it.next();
That will give us back our 1 from the yield 1 statment, but that's not the only thing we get back.
console.log(message); // { value:1, done:false }
We actually get back an object from each next() call, which has a value property for the yielded-out value, and done is a boolean that indicates if the generator function has fully completed or not.
Let's keep going with our iteration:
console.log( it.next() ); // { value:2, done:false } console.log( it.next() ); // { value:3, done:false } console.log( it.next() ); // { value:4, done:false } console.log( it.next() ); // { value:5, done:false }
Interesting to note, done is still false when we get the value of 5 out. That's because technically, the generator function is not complete. We still have to call a final next() call, and if we send in a value, it has to be set as the result of that yield 5 expression. Only then is the generator function complete.
So, now:
console.log( it.next() ); // { value:undefined, done:true }
So, the final result of our generator function was that we completed the function, but there was no result given (since we'd already exhausted all the yield ___ statements).
You may wonder at this point, can I use return from a generator function, and if I do, does that value get sent out in the value property?
Yes...
function *foo() { yield 1; return 2; } var it = foo(); console.log( it.next() ); // { value:1, done:false } console.log( it.next() ); // { value:2, done:true }
... and no.
It may not be a good idea to rely on the return value from generators, because when iterating generator functions with for..of loops (see below), the final returned value would be thrown away.
For completeness sake, let's also take a look at sending messages both into and out of a generator function as we iterate it:
function *foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var it = foo( 5 ); // note: not sending anything into `next()` here console.log( it.next() ); // { value:6, done:false } console.log( it.next( 12 ) ); // { value:8, done:false } console.log( it.next( 13 ) ); // { value:42, done:true }
You can see that we can still pass in parameters (x in our example) with the initial foo( 5 ) iterator-instantiation call, just like with normal functions, making x be value 5.
The first next(..) call, we don't send in anything. Why? Because there's no yieldexpression to receive what we pass in.
But if we did pass in a value to that first next(..) call, nothing bad would happen. It would just be a tossed-away value. ES6 says for generator functions to ignore the unused value in this case. (Note: At the time of writing, nightlies of both Chrome and FF are fine, but other browsers may not yet be fully compliant and may incorrectly throw an error in this case).
The yield (x + 1) is what sends out value 6. The second next(12) call sends 12 to that waiting yield (x + 1) expression, so y is set to 12 * 2, value 24. Then the subsequent yield (y / 3) (yield (24 / 3)) is what sends out the value 8. The third next(13) call sends 13 to that waiting yield (y / 3) expression, making z set to 13.
Finally, return (x + y + z) is return (5 + 24 + 13), or 42 being returned out as the last value.
Re-read that a few times. It's weird for most, the first several times they see it.
ES6 also embraces this iterator pattern at the syntactic level, by providing direct support for running iterators to completion: the for..of loop.
Example:
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (var v of foo()) { console.log( v ); } // 1 2 3 4 5 console.log( v ); // still `5`, not `6` :(
As you can see, the iterator created by foo() is automatically captured by the for..ofloop, and it's automatically iterated for you, one iteration for each value, until a done:truecomes out. As long as done is false, it automatically extracts the value property and assigns it to your iteration variable (v in our case). Once done is true, the loop iteration stops (and does nothing with any final value returned, if any).
As noted above, you can see that the for..of loop ignores and throws away the return 6value. Also, since there's no exposed next() call, the for..of loop cannot be used in situations where you need to pass in values to the generator steps as we did above.
OK, so that's it for the basics of generators. Don't worry if it's a little mind-bending still. All of us have felt that way at first!
It's natural to wonder what this new exotic toy is going to do practically for your code. There's a lot more to them, though. We've just scratched the surface. So we have to dive deeper before we can discover just how powerful they can/will be.
After you've played around with the above code snippets (try Chrome nightly/canary or FF nightly, or node 0.11+ with the --harmony flag), the following questions may arise:
Those questions, and more, will be covered in subsequent articles here, so stay tuned!